When developing applications within this repository, developers must assume a defense-in-depth posture where framework defaults handle basic output encoding, session handling, and database parameterization, but require explicit developer configuration for authorization scopes, input validation, and security headers. Security-sensitive surfaces include database queries, authentication plugs, file upload endpoints, custom channel joins, and runtime debug options. Mistakes regarding authorization, token verification, and redirect handling must always fail closed rather than assuming permissive defaults.
Essential implementation rules
Derive Authorization Scope from Server Assigns and Halt on Failure
Always derive authorization checks and database query scopes from server-side assigns like conn.assigns.current_user or %Scope{} structs rather than trusting client parameters. Explicitly call Plug.Conn.halt/1 inside custom authentication or authorization plugs to terminate request execution immediately upon failure, and enforce matching socket topic rules in channel join/3 callbacks.
Enforce Input Casting Boundaries and Strict Parameter Validation
Explicitly specify allowed input keys in Ecto.Changeset.cast/3 while excluding administrative or sensitive fields to prevent mass assignment, and apply strict constraints such as validate_length/3 and validate_number/3. Read parameter origins explicitly using origin-specific connection accessors like conn.body_params or conn.query_params to avoid parameter shadowing discrepancies.
Prevent SQL Injection and Unsafe Binary Deserialization
Always parameterize dynamic values in Ecto queries using Ecto query syntax, parameter bindings in fragment/2, or parameter placeholders in raw SQL queries instead of string interpolation. Avoid standard binary deserialization and use Plug.Crypto.non_executable_binary_to_term/2 with [:safe] for untrusted data.
Use HEEx Templates for Safe HTML Encoding and Avoid Dynamic Code Execution
Render untrusted data automatically and safely through HEEx templates and render/3 without relying on unescaped HTML functions. Never pass untrusted user input into dynamic code evaluation or operating system command execution functions such as Code.eval_string/3 or System.cmd/3.
Protect Session State, Tokens, and Credentials Securely
Use Argon2 as the password hashing library when generating authentication features, enforce require_sudo_mode for sensitive settings changes, and clear session state along with live socket disconnect broadcasts upon logout. Redact sensitive schema fields with redact: true and filter parameters from logs using Phoenix.Logger.filter_values/2.
Enforce CSRF Protection, Security Headers, and Transport Security
Include the protect_from_forgery plug and put_secure_browser_headers in all browser-facing pipelines while rendering CSRF token meta tags in root layouts. Configure endpoint :force_ssl in compile-time configuration to inject strict transport security headers.
Sanitize File Uploads and Validate Internal Redirects
Never rely directly on untrusted upload filenames from Plug.Upload, instead extracting metadata and assigning unique server-controlled identifiers when storing files, while enforcing explicit content type validations when serving them. Use relative path options for internal redirects to enforce server-side validation against open redirect attacks.
Harden Runtime Environments and Restrict Resource Consumption
Gate development-only routes and dashboards behind :dev_routes compile-time flags and keep debug_errors: false in production endpoints. Restrict local development listeners to loopback interfaces, set explicit channel transport limits on sockets, and enforce parser limits via Plug.Parsers to prevent resource exhaustion.
phoenix: All Security Cards
Approximately 5,185 tokens
On this card
Category: access control
Enforce Resource-Level Access Control and Ownership Scopes in Context Functions
Use when
Developing data layer context functions, controllers, or channel callbacks where database queries and resource actions must be restricted to the authenticated user or tenant.
Secure rules
Rule 1: Derive authorization context from server assigns and pass scope structs into context queries
Always perform access control checks using authenticated identity stored in server-side assigns such as conn.assigns.current_user or %Scope{} structs. Never rely on user-supplied parameters in request bodies or query parameters to determine authorization or identity, and ensure all queries filter records by matching the user or tenant ID.
def list_posts(%Scope{} = scope) do Repo.all(from post in Post, where: post.user_id == ^scope.user.id)end
Explicitly match topic patterns in join/3 callbacks and evaluate access control checks against the socket assigns before permitting topic subscriptions.
def join("room:" <> room_id, _params, socket) do if Accounts.can_access_room?(socket.assigns.current_user, room_id) do {:ok, socket} else {:error, %{reason: "unauthorized"}} endend
Halt Plug Execution on Access Control Failure
Use when
Writing custom authentication or authorization plugs where request execution must be immediately terminated upon failure.
Secure rules
Rule 1: Call Plug.Conn.halt when access checks fail in custom plugs
When writing custom authentication or authorization plugs, always call Plug.Conn.halt(conn) when access checks fail. Simply adding a redirect or flash message to the connection does not stop downstream plugs or controller actions from executing.
defp authenticate(conn, _) do case Authenticator.find_user(conn) do {:ok, user} -> assign(conn, :user, user) :error -> conn |> put_flash(:error, "You must be logged in") |> redirect(to: ~p"/login") |> halt() endend
Category: api contract misuse
Verify HTTP Status Codes for Controller Error Handling
Use when
When testing Phoenix controllers to ensure domain-level exceptions and resource lookup failures map correctly to expected HTTP status codes.
Secure rules
Rule 1: Use assert_error_sent to verify correct HTTP status mapping for resource lookup failures in controller tests.
Always verify that domain-level exceptions during request processing map to appropriate HTTP status codes like 404 rather than exposing internal failure details, by wrapping the request in assert_error_sent.
test "returns 404 for deleted or non-existent post", %{conn: conn, post: post} do conn = delete(conn, ~p"/posts/#{post}") assert redirected_to(conn) == ~p"/posts" assert_error_sent 404, fn -> get(conn, ~p"/posts/#{post}") endend
Category: authentication
Enforce Token Verification and Secure Credentials in Phoenix Authentication
Use when
Implementing user authentication, token-based verification, magic links, API bearer token validation, and secure socket connection options in Phoenix.
Secure rules
Rule 1: Enforce “sudo mode” with the generated require_sudo_mode plug
Protect routes that modify credentials, email addresses, or other critical data by adding the require_sudo_mode plug (generated by mix phx.gen.auth). The plug verifies the user has re-authenticated within Phoenix’s recent-login window and, if not, redirects them through the quick re-auth flow before allowing the action to proceed.
defmodule MyAppWeb.SettingsController do use MyAppWeb, :controller import MyAppWeb.UserAuth # brings require_sudo_mode/2 into scope plug :fetch_current_scope_for_user plug :require_authenticated_user plug :require_sudo_mode when action in [:edit_email, :update_email] def edit_email(conn, _params) do render(conn, :edit_email) end def update_email(conn, %{"user" => user_params}) do # Runs only after a fresh re-authentication ... endend
Rule 2: Extract and validate API Bearer tokens with explicit connection halting
Parse authorization headers within API pipeline plugs, assigning user scope on success and explicitly calling halt/1 on missing or invalid tokens.
Rule 3: Pass socket authentication tokens via the authToken constructor option
Supply sensitive authentication tokens through the authToken constructor option rather than URL query parameters to avoid leaking secrets into access logs or headers.
import { Socket } from "phoenix"const socket = new Socket("/socket", { authToken: () => window.userToken, params: { theme: "dark" }})socket.connect()
Category: boundary control
Validate internal redirect paths to prevent open redirects
Use when
Handling user-supplied return paths or redirect targets in Phoenix controllers to prevent open redirect vulnerabilities.
Secure rules
Rule 1: Use relative path options for internal redirects to enforce server-side validation against open redirect attacks.
Use redirect(conn, to: ...) for relative internal redirects to automatically block open redirect attacks. Phoenix strictly validates the path provided to :to, raising an ArgumentError on full URLs with hostnames, protocol-relative URLs, and encoding bypasses. Use :external explicitly when redirecting to trusted external URLs.
def handle_redirect(conn, %{"return_to" => return_to}) do # Safe: Phoenix rejects external or malformed URLs passed to :to redirect(conn, to: return_to)enddef handle_external_redirect(conn, %{"url" => url}) do # Safe: Require explicit opt-in via :external for trusted external URLs if trusted_domain?(url) do redirect(conn, external: url) else conn |> send_resp(400, "Invalid URL") endend
Category: cryptography
Select robust password hashing algorithms during generation
Use when
When generating authentication features or configuring password hashing for user credentials.
Secure rules
Rule 1: Use Argon2 as the password hashing library when server compute resources permit.
Pass the --hashing-lib argon2 flag when running mix phx.gen.auth to ensure user credentials are more resistant to offline brute-force attacks compared to standard bcrypt or pbkdf2.
$ mix phx.gen.auth Accounts User users --hashing-lib argon2
Category: csrf
Enforce CSRF Protection in Pipelines and Layouts
Use when
Configuring browser pipelines, session handling, and root layouts in Phoenix applications to prevent cross-site request forgery.
Secure rules
Rule 1: Include the protect_from_forgery plug in browser-facing pipelines and render CSRF token meta tags in layouts.
Ensure all browser pipelines execute the protect_from_forgery plug after fetching the session, and include the CSRF meta tag using get_csrf_token() within your root layout.
Prevent arbitrary code evaluation and command execution with untrusted input
Use when
Handling dynamic input that should be evaluated as code or passed to system execution functions.
Secure rules
Rule 1: Never pass untrusted user input to dynamic code evaluation or operating system command execution functions.
Avoid passing dynamic user strings to functions such as Code.eval_string/3, Code.eval_file/2, Code.eval_quoted/3, EEx.eval_string/3, EEx.eval_file/3, :os.cmd/2, System.cmd/3, or System.shell/2. Instead, perform safe pattern matching or explicit function lookup.
def process_data(data) do # Avoid passing dynamic user string to Code.eval_string/3 or System.shell/2 # Perform safe pattern matching or explicit function lookup instead case data do "action_a" -> ActionModule.action_a() "action_b" -> ActionModule.action_b() endend
Category: deserialization
Use safe binary deserialization for untrusted data
Use when
Deserializing binary data from untrusted sources in Phoenix or Plug applications.
Secure rules
Rule 1: Avoid binary_to_term and use non_executable_binary_to_term for untrusted data
Do not decode binary data from untrusted sources using :erlang.binary_to_term/2 even when passing the [:safe] option because it does not prevent the creation of executable terms. Use Plug.Crypto.non_executable_binary_to_term/2 with [:safe] instead.
Sanitize and validate uploaded filenames and content types
Use when
When handling user file uploads using Plug.Upload and persisting or serving files.
Secure rules
Rule 1: Sanitize untrusted filenames and assign unique server-controlled names when persisting uploaded files
Never rely directly on untrusted upload filenames provided by Plug.Upload to prevent path traversal or file overwrites. Extract only necessary metadata such as the file extension and assign unique server-controlled identifiers when storing files.
if upload = product_params["photo"] do extension = Path.extname(upload.filename) File.cp(upload.path, "/media/#{product.id}-cover#{extension}")end
Rule 2: Enforce explicit content type validation when serving uploaded user files
Validate allowed content types on user-uploaded files rather than relying blindly on metadata when responding with stored file data.
Parameterize dynamic database queries and avoid SQL string interpolation
Use when
Building dynamic queries and database fragments in applications using Ecto or raw SQL queries
Secure rules
Rule 1: Always parameterize dynamic values in Ecto queries using Ecto query syntax, parameter bindings in fragment/2, or parameter placeholders in raw SQL queries.
Prevent SQL injection vulnerabilities by ensuring untrusted user input is never directly interpolated into SQL strings or fragments. Use parameter binding mechanisms such as ^min_q or positional parameters like $1.
# Safe Ecto fragment bindingfrom(f in Fruit, where: fragment("f0.quantity >= ? AND f0.secret = FALSE", ^min_q))# Safe raw SQL query bindingEcto.Adapters.SQL.query(Repo, "SELECT * FROM fruits WHERE quantity > $1 AND secret = FALSE", [min_q])
Category: input contract definition
Validate and Cast Input Parameters Securely with Ecto Changesets
Use when
When handling external user input maps, form submissions, or API parameters in Ecto changesets before application processing and database operations.
Secure rules
Rule 1: Explicitly specify allowed input parameters and exclude administrative or sensitive fields in Ecto changeset casting.
Strictly specify allowed parameter keys in Ecto.Changeset.cast/3 calls. Never cast administrative, privilege-related, or sensitive server-managed fields to prevent mass assignment vulnerabilities.
def registration_changeset(user, attrs, opts \\ []) do user |> cast(attrs, [:email, :password]) # Exclude :is_admin |> validate_email() |> validate_password(opts)end
Rule 2: Enforce strict length and numeric bound validations on user input attributes.
Apply explicit constraint validations such as validate_length/3 and validate_number/3 to enforce required character limits and numeric bounds on input fields.
Access parameter origins explicitly to avoid parameter shadowing and parser discrepancies
Use when
Handling incoming request parameters in Phoenix controllers where distinct sources such as query parameters, path variables, and request bodies must be unambiguously separated.
Secure rules
Rule 1: Read parameter sources explicitly using origin-specific connection accessors instead of relying on the merged params map.
When an application depends on input coming specifically from the query string or the request body, avoid relying on the merged params map alone if parameter collisions could bypass logic. Instead, read parameter sources directly using conn.path_params, conn.body_params, or conn.query_params to prevent parameter shadowing.
defmodule HelloWeb.HelloController do use HelloWeb, :controller def create(conn, _params) do payload = conn.body_params["url"] query_token = conn.query_params["token"] case Hello.Urls.create_url(payload, query_token) do {:ok, url} -> render(conn, :show, url: url) {:error, changeset} -> render(conn, :error, changeset: changeset) end endend
Prevent prototype pollution during object serialization
Use when
Serializing nested parameter objects or dynamic dictionaries into query strings or socket parameters where prototype properties might be enumerated.
Secure rules
Rule 1: Check own-property ownership using Object.prototype.hasOwnProperty.call during object property enumeration.
When iterating over enumerable properties using for...in loops on dynamic dictionaries or parameter objects, always verify that keys belong directly to the object by using Object.prototype.hasOwnProperty.call(obj, key). This prevents prototype-polluted properties from being included in serialized request payloads.
static serialize(obj, parentKey) { let queryStr = []; for (var key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) { continue; } let paramKey = parentKey ? `${parentKey}[${key}]` : key; let paramVal = obj[key]; if (typeof paramVal === "object" && paramVal !== null) { queryStr.push(this.serialize(paramVal, paramKey)); } else { queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal)); } } return queryStr.join("&");}
Category: interface protocol hardening
Configure browser and transport security headers
Use when
Configuring router pipelines and endpoint transport settings to enforce browser security headers and strict transport security.
Secure rules
Rule 1: Include put_secure_browser_headers in browser pipelines
Ensure plug :put_secure_browser_headers is included in all router pipelines serving browser traffic. In Phoenix 1.8.9, put_secure_browser_headers applies standard HTTP security headers, defaulting content-security-policy to "base-uri 'self'; frame-ancestors 'self';" when unconfigured.
Rule 2: Enable force_ssl in compile-time config to inject Strict-Transport-Security headers
Configure endpoint :force_ssl in compile-time configuration files such as config/prod.exs (not config/runtime.exs) to ensure Phoenix emits the Strict-Transport-Security (HSTS) security header on HTTPS responses.
Restrict Endpoint Network Interfaces in Non-Production Environments
Use when
Configuring local development endpoint server listeners and network interface bindings in Phoenix applications.
Secure rules
Rule 1: Restrict local development endpoint server listeners to loopback IP addresses.
Configure wider network interface bindings only within runtime production configuration and explicitly set the loopback address in development endpoint settings using ip: {127, 0, 0, 1}.
Use Phoenix view rendering and HEEx templates for automatic HTML output encoding
Use when
Rendering dynamic HTML responses and user input in Phoenix views and templates.
Secure rules
Rule 1: Always use HEEx templates and render/3 to render untrusted data automatically and safely escape it.
Pass user parameters directly into HEEx templates or use render/3 to benefit from Phoenix’s built-in automatic output encoding. Avoid passing untrusted input into raw/1 and do not manually construct unescaped HTML strings in controllers.
def show(conn, %{"messenger" => messenger}) do render(conn, :show, messenger: messenger)end
Category: resource exhaustion
Limit socket channel counts and request parsing sizes to prevent resource exhaustion
Use when
Configuring Phoenix sockets, transports, and endpoint parsers to handle incoming client connections and payloads safely.
Secure rules
Rule 1: Configure maximum channel limits per transport on Phoenix sockets to prevent process exhaustion.
Set max_channels_per_transport on Phoenix.Socket definitions to restrict how many concurrent channels a single socket connection can join. This mitigates risks where malicious clients attempt to spawn thousands of BEAM process instances and exhaust server memory.
defmodule MyAppWeb.UserSocket do use Phoenix.Socket, max_channels_per_transport: 20 channel "room:*", MyAppWeb.RoomChannel def connect(_params, socket, _connect_info) do {:ok, socket} end def id(_socket), do: nilend
Rule 2: Configure upload size, read timeout, and parser limits in Plug.Parsers.
Set explicit constraints like :length, :read_length, and :read_timeout on Plug.Parsers to protect against slowloris attacks and excessive resource consumption from large request payloads.
Disable Sensitive Debug Options and Endpoints in Production Environments
Use when
Configuring runtime environments, endpoints, and database connections for production deployments.
Secure rules
Rule 1: Gate development-only routes behind the :dev_routes compile-time flag
Expose diagnostic routes such as Phoenix LiveDashboard and Swoosh mailbox previewonly when the application is built with dev_routes: true.
Leave the flag unset in production configs so the code below is not compiled into the release.
# config/dev.exs (compile-time)config :my_app, dev_routes: true # enabled only for development# lib/my_app_web/router.exif Application.compile_env(:my_app, :dev_routes) do import Phoenix.LiveDashboard.Router scope "/dev" do pipe_through :browser live_dashboard "/dashboard", metrics: MyAppWeb.Telemetry forward "/mailbox", Plug.Swoosh.MailboxPreview endend
Rule 2: Disable :debug_errors in production endpoints and rely on :render_errors for sanitized pages
Do not expose detailed stack traces or source code in production. Ensure debug_errors: false (the default) in your production Endpoint configuration and define render_errors to control how friendly error pages are rendered.
# config/prod.exsconfig :my_app, MyAppWeb.Endpoint, url: [host: "example.com", port: 443], # Leave debug_errors at its secure default (false) or set it explicitly debug_errors: false, # Use ErrorHTML / ErrorJSON views to present sanitized messages render_errors: [ formats: [html: MyApp.ErrorHTML, json: MyApp.ErrorJSON], layout: false ]
Category: secret handling
Redact Sensitive Parameter Values in Application Logs
Use when
When processing or logging request parameters, custom data maps, or schema fields containing sensitive credentials and tokens.
Secure rules
Rule 1: Filter sensitive parameter values in application logs to prevent secret leakage
Use Phoenix.Logger.filter_values/2 or precompiled filters created with Phoenix.Logger.compile_filter/1 to scrub sensitive data such as passwords and tokens from logs.
Rule 2: Redact sensitive schema fields with redact: true
When defining Ecto schemas, mark secrets such as raw passwords and password hashes with redact: true. Ecto will automatically derive the Inspect protocol for the struct, omitting redacted fields from logs and interactive consoles, so sensitive data never appears in inspect output.
defmodule MyApp.Accounts.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :email, :string field :password, :string, virtual: true, redact: true field :hashed_password, :string, redact: true field :confirmed_at, :utc_datetime_usec timestamps() endend
Category: session management
Clear Session State and Terminate Live Sockets on Logout
Use when
Implementing user logout flows in Phoenix web applications to ensure session state is completely invalidated and active WebSocket connections are terminated.
Secure rules
Rule 1: Erase HTTP session data, delete session tokens from storage, and broadcast a disconnect event to active live sockets upon logout.
When logging out a user, invoke clear_session/1, delete the resp cookie, and broadcast a disconnect message to the live_socket_id to terminate open channels and prevent token reuse.
def log_out_user(conn) do user_token = get_session(conn, :user_token) user_token && Accounts.delete_user_session_token(user_token) if live_socket_id = get_session(conn, :live_socket_id) do YourAppWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{}) end conn |> delete_resp_cookie(@remember_me_cookie) |> clear_session() |> redirect(to: ~p"/")end
Enforce Resource-Level Access Control and Ownership Scopes in Context Functions
Approximately 487 tokens
Use when
Developing data layer context functions, controllers, or channel callbacks where database queries and resource actions must be restricted to the authenticated user or tenant.
Secure rules
Rule 1: Derive authorization context from server assigns and pass scope structs into context queries
Always perform access control checks using authenticated identity stored in server-side assigns such as conn.assigns.current_user or %Scope{} structs. Never rely on user-supplied parameters in request bodies or query parameters to determine authorization or identity, and ensure all queries filter records by matching the user or tenant ID.
def list_posts(%Scope{} = scope) do Repo.all(from post in Post, where: post.user_id == ^scope.user.id)end
Explicitly match topic patterns in join/3 callbacks and evaluate access control checks against the socket assigns before permitting topic subscriptions.
def join("room:" <> room_id, _params, socket) do if Accounts.can_access_room?(socket.assigns.current_user, room_id) do {:ok, socket} else {:error, %{reason: "unauthorized"}} endend
Halt Plug Execution on Access Control Failure
Use when
Writing custom authentication or authorization plugs where request execution must be immediately terminated upon failure.
Secure rules
Rule 1: Call Plug.Conn.halt when access checks fail in custom plugs
When writing custom authentication or authorization plugs, always call Plug.Conn.halt(conn) when access checks fail. Simply adding a redirect or flash message to the connection does not stop downstream plugs or controller actions from executing.
defp authenticate(conn, _) do case Authenticator.find_user(conn) do {:ok, user} -> assign(conn, :user, user) :error -> conn |> put_flash(:error, "You must be logged in") |> redirect(to: ~p"/login") |> halt() endend
Verify HTTP Status Codes for Controller Error Handling
Approximately 215 tokens
Use when
When testing Phoenix controllers to ensure domain-level exceptions and resource lookup failures map correctly to expected HTTP status codes.
Secure rules
Rule 1: Use assert_error_sent to verify correct HTTP status mapping for resource lookup failures in controller tests.
Always verify that domain-level exceptions during request processing map to appropriate HTTP status codes like 404 rather than exposing internal failure details, by wrapping the request in assert_error_sent.
test "returns 404 for deleted or non-existent post", %{conn: conn, post: post} do conn = delete(conn, ~p"/posts/#{post}") assert redirected_to(conn) == ~p"/posts" assert_error_sent 404, fn -> get(conn, ~p"/posts/#{post}") endend
Enforce Token Verification and Secure Credentials in Phoenix Authentication
Approximately 558 tokens
Use when
Implementing user authentication, token-based verification, magic links, API bearer token validation, and secure socket connection options in Phoenix.
Secure rules
Rule 1: Enforce “sudo mode” with the generated require_sudo_mode plug
Protect routes that modify credentials, email addresses, or other critical data by adding the require_sudo_mode plug (generated by mix phx.gen.auth). The plug verifies the user has re-authenticated within Phoenix’s recent-login window and, if not, redirects them through the quick re-auth flow before allowing the action to proceed.
defmodule MyAppWeb.SettingsController do use MyAppWeb, :controller import MyAppWeb.UserAuth # brings require_sudo_mode/2 into scope plug :fetch_current_scope_for_user plug :require_authenticated_user plug :require_sudo_mode when action in [:edit_email, :update_email] def edit_email(conn, _params) do render(conn, :edit_email) end def update_email(conn, %{"user" => user_params}) do # Runs only after a fresh re-authentication ... endend
Rule 2: Extract and validate API Bearer tokens with explicit connection halting
Parse authorization headers within API pipeline plugs, assigning user scope on success and explicitly calling halt/1 on missing or invalid tokens.
Rule 3: Pass socket authentication tokens via the authToken constructor option
Supply sensitive authentication tokens through the authToken constructor option rather than URL query parameters to avoid leaking secrets into access logs or headers.
import { Socket } from "phoenix"const socket = new Socket("/socket", { authToken: () => window.userToken, params: { theme: "dark" }})socket.connect()
Validate internal redirect paths to prevent open redirects
Approximately 278 tokens
Use when
Handling user-supplied return paths or redirect targets in Phoenix controllers to prevent open redirect vulnerabilities.
Secure rules
Rule 1: Use relative path options for internal redirects to enforce server-side validation against open redirect attacks.
Use redirect(conn, to: ...) for relative internal redirects to automatically block open redirect attacks. Phoenix strictly validates the path provided to :to, raising an ArgumentError on full URLs with hostnames, protocol-relative URLs, and encoding bypasses. Use :external explicitly when redirecting to trusted external URLs.
def handle_redirect(conn, %{"return_to" => return_to}) do # Safe: Phoenix rejects external or malformed URLs passed to :to redirect(conn, to: return_to)enddef handle_external_redirect(conn, %{"url" => url}) do # Safe: Require explicit opt-in via :external for trusted external URLs if trusted_domain?(url) do redirect(conn, external: url) else conn |> send_resp(400, "Invalid URL") endend
Select robust password hashing algorithms during generation
Approximately 149 tokens
Use when
When generating authentication features or configuring password hashing for user credentials.
Secure rules
Rule 1: Use Argon2 as the password hashing library when server compute resources permit.
Pass the --hashing-lib argon2 flag when running mix phx.gen.auth to ensure user credentials are more resistant to offline brute-force attacks compared to standard bcrypt or pbkdf2.
mix phx.gen.auth Accounts User users --hashing-lib argon2
Enforce CSRF Protection in Pipelines and Layouts
Approximately 207 tokens
Use when
Configuring browser pipelines, session handling, and root layouts in Phoenix applications to prevent cross-site request forgery.
Secure rules
Rule 1: Include the protect_from_forgery plug in browser-facing pipelines and render CSRF token meta tags in layouts.
Ensure all browser pipelines execute the protect_from_forgery plug after fetching the session, and include the CSRF meta tag using get_csrf_token() within your root layout.
Prevent arbitrary code evaluation and command execution with untrusted input
Approximately 249 tokens
Use when
Handling dynamic input that should be evaluated as code or passed to system execution functions.
Secure rules
Rule 1: Never pass untrusted user input to dynamic code evaluation or operating system command execution functions.
Avoid passing dynamic user strings to functions such as Code.eval_string/3, Code.eval_file/2, Code.eval_quoted/3, EEx.eval_string/3, EEx.eval_file/3, :os.cmd/2, System.cmd/3, or System.shell/2. Instead, perform safe pattern matching or explicit function lookup.
def process_data(data) do # Avoid passing dynamic user string to Code.eval_string/3 or System.shell/2 # Perform safe pattern matching or explicit function lookup instead case data do "action_a" -> ActionModule.action_a() "action_b" -> ActionModule.action_b() endend
Use safe binary deserialization for untrusted data
Approximately 175 tokens
Use when
Deserializing binary data from untrusted sources in Phoenix or Plug applications.
Secure rules
Rule 1: Avoid binary_to_term and use non_executable_binary_to_term for untrusted data
Do not decode binary data from untrusted sources using :erlang.binary_to_term/2 even when passing the [:safe] option because it does not prevent the creation of executable terms. Use Plug.Crypto.non_executable_binary_to_term/2 with [:safe] instead.
Sanitize and validate uploaded filenames and content types
Approximately 315 tokens
Use when
When handling user file uploads using Plug.Upload and persisting or serving files.
Secure rules
Rule 1: Sanitize untrusted filenames and assign unique server-controlled names when persisting uploaded files
Never rely directly on untrusted upload filenames provided by Plug.Upload to prevent path traversal or file overwrites. Extract only necessary metadata such as the file extension and assign unique server-controlled identifiers when storing files.
if upload = product_params["photo"] do extension = Path.extname(upload.filename) File.cp(upload.path, "/media/#{product.id}-cover#{extension}")end
Rule 2: Enforce explicit content type validation when serving uploaded user files
Validate allowed content types on user-uploaded files rather than relying blindly on metadata when responding with stored file data.
Parameterize dynamic database queries and avoid SQL string interpolation
Approximately 218 tokens
Use when
Building dynamic queries and database fragments in applications using Ecto or raw SQL queries
Secure rules
Rule 1: Always parameterize dynamic values in Ecto queries using Ecto query syntax, parameter bindings in fragment/2, or parameter placeholders in raw SQL queries.
Prevent SQL injection vulnerabilities by ensuring untrusted user input is never directly interpolated into SQL strings or fragments. Use parameter binding mechanisms such as ^min_q or positional parameters like $1.
# Safe Ecto fragment bindingfrom(f in Fruit, where: fragment("f0.quantity >= ? AND f0.secret = FALSE", ^min_q))# Safe raw SQL query bindingEcto.Adapters.SQL.query(Repo, "SELECT * FROM fruits WHERE quantity > $1 AND secret = FALSE", [min_q])
Validate and Cast Input Parameters Securely with Ecto Changesets
Approximately 293 tokens
Use when
When handling external user input maps, form submissions, or API parameters in Ecto changesets before application processing and database operations.
Secure rules
Rule 1: Explicitly specify allowed input parameters and exclude administrative or sensitive fields in Ecto changeset casting.
Strictly specify allowed parameter keys in Ecto.Changeset.cast/3 calls. Never cast administrative, privilege-related, or sensitive server-managed fields to prevent mass assignment vulnerabilities.
def registration_changeset(user, attrs, opts \\ []) do user |> cast(attrs, [:email, :password]) # Exclude :is_admin |> validate_email() |> validate_password(opts)end
Rule 2: Enforce strict length and numeric bound validations on user input attributes.
Apply explicit constraint validations such as validate_length/3 and validate_number/3 to enforce required character limits and numeric bounds on input fields.
Access parameter origins explicitly to avoid parameter shadowing and parser discrepancies
Approximately 542 tokens
Use when
Handling incoming request parameters in Phoenix controllers where distinct sources such as query parameters, path variables, and request bodies must be unambiguously separated.
Secure rules
Rule 1: Read parameter sources explicitly using origin-specific connection accessors instead of relying on the merged params map.
When an application depends on input coming specifically from the query string or the request body, avoid relying on the merged params map alone if parameter collisions could bypass logic. Instead, read parameter sources directly using conn.path_params, conn.body_params, or conn.query_params to prevent parameter shadowing.
defmodule HelloWeb.HelloController do use HelloWeb, :controller def create(conn, _params) do payload = conn.body_params["url"] query_token = conn.query_params["token"] case Hello.Urls.create_url(payload, query_token) do {:ok, url} -> render(conn, :show, url: url) {:error, changeset} -> render(conn, :error, changeset: changeset) end endend
Prevent prototype pollution during object serialization
Use when
Serializing nested parameter objects or dynamic dictionaries into query strings or socket parameters where prototype properties might be enumerated.
Secure rules
Rule 1: Check own-property ownership using Object.prototype.hasOwnProperty.call during object property enumeration.
When iterating over enumerable properties using for...in loops on dynamic dictionaries or parameter objects, always verify that keys belong directly to the object by using Object.prototype.hasOwnProperty.call(obj, key). This prevents prototype-polluted properties from being included in serialized request payloads.
static serialize(obj, parentKey) { let queryStr = []; for (var key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) { continue; } let paramKey = parentKey ? `${parentKey}[${key}]` : key; let paramVal = obj[key]; if (typeof paramVal === "object" && paramVal !== null) { queryStr.push(this.serialize(paramVal, paramKey)); } else { queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal)); } } return queryStr.join("&");}
Configure browser and transport security headers
Approximately 312 tokens
Use when
Configuring router pipelines and endpoint transport settings to enforce browser security headers and strict transport security.
Secure rules
Rule 1: Include put_secure_browser_headers in browser pipelines
Ensure plug :put_secure_browser_headers is included in all router pipelines serving browser traffic. In Phoenix 1.8.9, put_secure_browser_headers applies standard HTTP security headers, defaulting content-security-policy to "base-uri 'self'; frame-ancestors 'self';" when unconfigured.
Rule 2: Enable force_ssl in compile-time config to inject Strict-Transport-Security headers
Configure endpoint :force_ssl in compile-time configuration files such as config/prod.exs (not config/runtime.exs) to ensure Phoenix emits the Strict-Transport-Security (HSTS) security header on HTTPS responses.
Restrict Endpoint Network Interfaces in Non-Production Environments
Approximately 168 tokens
Use when
Configuring local development endpoint server listeners and network interface bindings in Phoenix applications.
Secure rules
Rule 1: Restrict local development endpoint server listeners to loopback IP addresses.
Configure wider network interface bindings only within runtime production configuration and explicitly set the loopback address in development endpoint settings using ip: {127, 0, 0, 1}.
Use Phoenix view rendering and HEEx templates for automatic HTML output encoding
Approximately 181 tokens
Use when
Rendering dynamic HTML responses and user input in Phoenix views and templates.
Secure rules
Rule 1: Always use HEEx templates and render/3 to render untrusted data automatically and safely escape it.
Pass user parameters directly into HEEx templates or use render/3 to benefit from Phoenix’s built-in automatic output encoding. Avoid passing untrusted input into raw/1 and do not manually construct unescaped HTML strings in controllers.
def show(conn, %{"messenger" => messenger}) do render(conn, :show, messenger: messenger)end
Limit socket channel counts and request parsing sizes to prevent resource exhaustion
Approximately 356 tokens
Use when
Configuring Phoenix sockets, transports, and endpoint parsers to handle incoming client connections and payloads safely.
Secure rules
Rule 1: Configure maximum channel limits per transport on Phoenix sockets to prevent process exhaustion.
Set max_channels_per_transport on Phoenix.Socket definitions to restrict how many concurrent channels a single socket connection can join. This mitigates risks where malicious clients attempt to spawn thousands of BEAM process instances and exhaust server memory.
defmodule MyAppWeb.UserSocket do use Phoenix.Socket, max_channels_per_transport: 20 channel "room:*", MyAppWeb.RoomChannel def connect(_params, socket, _connect_info) do {:ok, socket} end def id(_socket), do: nilend
Rule 2: Configure upload size, read timeout, and parser limits in Plug.Parsers.
Set explicit constraints like :length, :read_length, and :read_timeout on Plug.Parsers to protect against slowloris attacks and excessive resource consumption from large request payloads.
Disable Sensitive Debug Options and Endpoints in Production Environments
Approximately 430 tokens
Use when
Configuring runtime environments, endpoints, and database connections for production deployments.
Secure rules
Rule 1: Gate development-only routes behind the :dev_routes compile-time flag
Expose diagnostic routes such as Phoenix LiveDashboard and Swoosh mailbox previewonly when the application is built with dev_routes: true.
Leave the flag unset in production configs so the code below is not compiled into the release.
# config/dev.exs (compile-time)config :my_app, dev_routes: true # enabled only for development# lib/my_app_web/router.exif Application.compile_env(:my_app, :dev_routes) do import Phoenix.LiveDashboard.Router scope "/dev" do pipe_through :browser live_dashboard "/dashboard", metrics: MyAppWeb.Telemetry forward "/mailbox", Plug.Swoosh.MailboxPreview endend
Rule 2: Disable :debug_errors in production endpoints and rely on :render_errors for sanitized pages
Do not expose detailed stack traces or source code in production. Ensure debug_errors: false (the default) in your production Endpoint configuration and define render_errors to control how friendly error pages are rendered.
# config/prod.exsconfig :my_app, MyAppWeb.Endpoint, url: [host: "example.com", port: 443], # Leave debug_errors at its secure default (false) or set it explicitly debug_errors: false, # Use ErrorHTML / ErrorJSON views to present sanitized messages render_errors: [ formats: [html: MyApp.ErrorHTML, json: MyApp.ErrorJSON], layout: false ]
Redact Sensitive Parameter Values in Application Logs
Approximately 327 tokens
Use when
When processing or logging request parameters, custom data maps, or schema fields containing sensitive credentials and tokens.
Secure rules
Rule 1: Filter sensitive parameter values in application logs to prevent secret leakage
Use Phoenix.Logger.filter_values/2 or precompiled filters created with Phoenix.Logger.compile_filter/1 to scrub sensitive data such as passwords and tokens from logs.
Rule 2: Redact sensitive schema fields with redact: true
When defining Ecto schemas, mark secrets such as raw passwords and password hashes with redact: true. Ecto will automatically derive the Inspect protocol for the struct, omitting redacted fields from logs and interactive consoles, so sensitive data never appears in inspect output.
defmodule MyApp.Accounts.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :email, :string field :password, :string, virtual: true, redact: true field :hashed_password, :string, redact: true field :confirmed_at, :utc_datetime_usec timestamps() endend
Clear Session State and Terminate Live Sockets on Logout
Approximately 253 tokens
Use when
Implementing user logout flows in Phoenix web applications to ensure session state is completely invalidated and active WebSocket connections are terminated.
Secure rules
Rule 1: Erase HTTP session data, delete session tokens from storage, and broadcast a disconnect event to active live sockets upon logout.
When logging out a user, invoke clear_session/1, delete the resp cookie, and broadcast a disconnect message to the live_socket_id to terminate open channels and prevent token reuse.
def log_out_user(conn) do user_token = get_session(conn, :user_token) user_token && Accounts.delete_user_session_token(user_token) if live_socket_id = get_session(conn, :live_socket_id) do YourAppWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{}) end conn |> delete_resp_cookie(@remember_me_cookie) |> clear_session() |> redirect(to: ~p"/")end