Safeguard
Application Security•

How do you prevent SQL injection in Elixir with Ecto?

Ecto binds pinned values as parameters. Injection risk returns with string building, identifier, constant and LIKE patterns. Know where each applies.

Safeguard Security Team
4 min read

In Elixir, prevent SQL injection by passing user input to Ecto through the pin operator so it is sent as a query parameter, never by building query strings. Take extra care with fragment, identifier, constant and LIKE patterns, where the documentation says values go into the query text or need escaping.

What does Ecto do by default?

Ecto's query API lets you write queries as code and interpolate values with the pin operator. Pinned values are passed as query parameters, which keeps data separate from SQL text. That is the safe path, and most code should stay on it.

What is fragment, and where does it matter?

Fragment sends a raw expression to the database. The documentation example is fragment("lower(?)", p.title) == ^title. Each question mark is a placeholder for an argument, and pinned arguments are bound as parameters, as in fragment("? COLLATE ?", ^name, ^collation).

Two details in the docs matter for security. Ecto cannot do type casting inside fragments, so you may need type/2. And a literal question mark must be escaped, which the docs say takes one escape for strings and another for the fragment.

The danger is the fragment's first argument. It is SQL text. If you build that string from user input, for example by concatenating a column name, you have written an injection flaw. Keep the fragment string a literal in your source.

What are identifier and constant?

The docs describe identifier(^value), which injects a value as an identifier, and constant(^value), which injects it as a literal. Unlike ordinary pinned values, both place the value directly into the query text instead of binding it. The page says Ecto handles quoting and escaping where necessary, and notes that each distinct value produces its own cached query.

So when you let a client choose a sort column, do not pass the raw string. A constructed example: an endpoint accepts a sort parameter. Compare it against a fixed allowlist of column atoms in code, and use only the matching atom in the query. Anything else returns an error. Allowlisting is our recommendation; the Ecto page does not state it as a rule.

What about LIKE?

The only explicit injection style warning on the Ecto query API page we read concerns like: be very careful when allowing user sent data to be used as part of a LIKE query. Characters such as percent and underscore are wildcards, so a user who sends them changes what the pattern matches. Escape them before you build the pattern, and check the Ecto docs for how to do so for your database.

What did we not confirm?

We tried to read the documentation for raw SQL queries through the Ecto SQL adapter and the page was not available, so this post says nothing about that API beyond the general rule: if you run raw SQL, pass parameters through placeholders and never join strings. Check the current adapter docs for your database's placeholder syntax.

How do you review for it?

Search the code for fragment, identifier, constant and any raw query call. For each, trace where the first argument and any pinned value come from. A literal string and pinned data is fine. A computed string is a finding.

What to do

  1. Keep all user values behind the pin operator.
  2. Keep fragment strings as literals in source.
  3. Allowlist anything that becomes an identifier or constant, such as sort columns.
  4. Escape wildcard characters in LIKE input.
  5. Add tests that send quotes, semicolons and wildcard characters and check results are unchanged.
  6. Use a least privilege database user for the application, so a flaw has limited reach.
  7. Scan dependencies for known issues in your database drivers and Ecto itself.

Safeguard's SAST code scan and dependency scanning can flag risky patterns and known vulnerable packages in a Mix project.

Quick check

Is fragment unsafe?

Not by itself. Pinned arguments are bound as parameters. It becomes unsafe if you build the fragment text from user input.

Does identifier bind its value as a parameter?

No. The docs say identifier and constant place the value directly into the query text, so only pass values you control.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.