Phoenix templates escape output by default, which blocks most cross-site scripting. The risk comes from places where you turn escaping off or output into a context HTML escaping does not cover: raw, URLs, inline scripts and sanitized rich text. Review those spots, and add a content security policy as a second layer.
What does Phoenix do by default?
The Phoenix.HTML docs state that data output in templates is not considered safe by default. Their example: output of the string hello wrapped in angle brackets is displayed as escaped entities, not as a tag. They add that user data or data from the database is almost never considered safe.
So a plain output expression in a template protects text placed between tags. That is the common case, and it is why XSS in Phoenix is less frequent than in frameworks where escaping is opt in.
Which escape hatches should I search for?
The same docs describe raw, which marks content as raw so it is not escaped. Each use deserves a question: where did this string come from, and who can influence it?
Constructed example. A comments page renders each body with raw so users can use bold text. A visitor posts a comment containing a script element. Every reader who opens the page runs it. The fix is not a cleverer filter. Store the comment as plain text and render it escaped, or run it through a maintained HTML sanitizer with a strict allow list before it is stored or rendered.
Other things to grep for:
- Any function that builds a tuple tagged as safe by hand.
- Helpers that create markup from strings, especially in LiveView components.
- Inline script blocks that interpolate server data.
What about contexts HTML escaping misses?
The OWASP XSS Prevention Cheat Sheet explains that the right encoding depends on where the data lands: HTML body, attribute, JavaScript, CSS or URL. It warns about dangerous contexts such as event handler attributes, javascript: URLs, and unsafe functions like eval, and says untrusted URL values need application level validation even where a framework blocks some cases.
Phoenix.HTML ships helpers for specific contexts, including attributes_escape, css_escape and javascript_escape. The docs describe javascript_escape as escaping HTML content to be inserted into a JavaScript string, for responses where you embed rendered HTML.
The practical rule: a link built from user input needs a scheme check. Allow only http and https, then render. Do not trust escaping alone for href.
How do I review a pull request for XSS?
Read the diff for three things. First, any new raw or safe tuple, and ask where its input originates. Second, any new link or redirect built from a parameter. Third, any new place where server data is placed inside a script block, style block or event attribute. A reviewer who checks only those three catches most of what slips past default escaping, and the habit costs minutes.
Automated static analysis for Elixir web apps can flag raw and similar patterns, but a human still has to judge the data source.
Do cookies and CSP help?
OWASP says cookie attributes limit the impact of XSS but do not prevent it, and that a content security policy is an allow list that should be a defense in depth layer, not your primary defense. Set the policy as a response header in your browser pipeline. Start in report only mode, read the reports, then enforce.
What to do
- Search the codebase for raw and for hand built safe tuples. List each with its data source.
- Replace raw with escaped output, or sanitize with a strict allow list.
- Validate URL schemes for any link built from user input.
- Never interpolate user data into inline scripts. Pass it through data attributes and read it in a script file.
- Add a content security policy, report only first.
- Add a test that posts a script payload to each rich text feature and asserts it is not executed.
- Keep Phoenix and its HTML packages current and scan dependencies.
Quick check
Is a plain output expression in a Phoenix template safe?
For text and most attribute values, yes, it is escaped. Special contexts like URLs and scripts still need extra care.
Is raw ever fine?
Only for content you generate or fully sanitize on the server, never for user supplied strings.
Sources
- Phoenix.HTML documentation
- OWASP Cross Site Scripting Prevention Cheat Sheet
- Plug.Conn documentation
Last reviewed: 18 October 2026.