Laravel XSS Prevention Guide: Blade Escaping, {!! !!} Risks & CSP Headers

Prevent cross-site scripting in Laravel. Learn when {!! !!} is safe, how to sanitize HTML input, encode output in Blade templates, and add Content Security Policy headers.

High severity Application Security Updated 2026-03-01 Markdown

Cross-site scripting is one of the most consistently exploited vulnerabilities on the web, appearing in every OWASP Top 10. Unlike server-side attacks that target your infrastructure, XSS targets your users — injecting JavaScript into your pages that executes in their browsers within the context of your trusted domain. There are three types: stored XSS (injected content saved to the database and rendered to all visitors), reflected XSS (injected content in a URL parameter reflected in the response), and DOM-based XSS (content manipulated entirely in client-side JavaScript without a server round-trip).

The consequences of successful XSS extend far beyond the classic "showing an alert box" demonstration. An attacker with XSS can read session cookies if HttpOnly is not set, capture every keystroke including passwords, silently redirect users to phishing pages, make authenticated API requests on behalf of the victim, modify page content to display fake information (such as fraudulent account balances), and use the victim's browser as a proxy to attack other internal systems. Stored XSS is especially destructive because a single injected payload affects every user who views the infected content.

Laravel's Blade templating engine uses {{ }} double curly braces that HTML-encode output by default, providing strong protection in standard HTML contexts. The vulnerability almost always appears when developers use {!! !!} (unescaped output) for content assumed to be safe, or when outputting data into JavaScript contexts. Rich text editors, Markdown renderers, and user profile fields that allow formatting are the highest-risk areas because they intentionally render HTML — and that HTML must be sanitized before storage and display.

The Problem

Cross-site scripting (XSS) vulnerabilities allow attackers to inject malicious JavaScript into web pages viewed by your users, enabling session hijacking, credential theft, and page defacement. While Laravel's Blade engine escapes output by default with {{ }}, XSS is introduced when developers use unescaped output {!! !!}, render user input in JavaScript contexts, or output data in HTML attributes without proper encoding.

How to Fix

  1. 1

    Always use escaped Blade output

    Use {{ }} (double curly braces) for all user-generated content. This automatically HTML-encodes the output:

    {{-- SAFE - output is escaped --}}
    <p>{{ $user->name }}</p>
    <p>{{ $comment->body }}</p>
    {{-- VULNERABLE - output is raw/unescaped --}}
    <p>{!! $user->bio !!}</p>
    Only use {!! !!} for content you have explicitly sanitized or that you generated yourself (like rendered Markdown from trusted sources).
  2. 2

    Sanitize HTML when you must allow it

    If you need to allow HTML (rich text editors, Markdown), sanitize it before storage and display:

    composer require mews/purifier

    Then sanitize on input:

    use Mews\Purifier\Facades\Purifier;
    $cleanHtml = Purifier::clean($request->input('body'));
    $post->body = $cleanHtml;

    Configure allowed tags in config/purifier.php:

    'HTML.Allowed' => 'p,br,b,i,strong,em,a[href],ul,ol,li,h2,h3,blockquote,pre,code',
    Always sanitize on input (before saving) and still use {!! !!} only with sanitized content.
  3. 3

    Handle JavaScript context safely

    Never output user data directly into JavaScript:

    {{-- VULNERABLE --}}
    <script>
    var name = '{{ $user->name }}';
    </script>
    {{-- SAFE - use @json which applies JSON encoding --}}
    <script>
    var name = @json($user->name);
    var userData = @json($user->only(['name', 'email']));
    </script>

    The @json directive properly escapes data for JavaScript contexts, preventing injection through string breakout.

  4. 4

    Add Content-Security-Policy header

    Add a CSP header to prevent inline script execution, which blocks most XSS attacks even if a vulnerability exists:

    Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
    With CSP in place, injected scripts cannot execute because the browser blocks inline JavaScript. This provides defense-in-depth alongside proper output encoding.

    See the missing-security-headers guide for full middleware implementation.

How to Verify

Test by entering XSS payloads in your forms:

<script>alert('xss')</script>
<img src=x onerror=alert('xss')>
<svg onload=alert('xss')>

The text should be displayed literally on the page (you see the HTML tags as text) and never execute as JavaScript. Check the page source to verify the output is HTML-encoded.

Prevention

Establish a coding standard that requires code review approval for any use of {!! !!} in Blade templates. Use @json for JavaScript context output. Add Content-Security-Policy headers. Run automated XSS scanning tools like OWASP ZAP against your application regularly.

Frequently Asked Questions

Does Blade's {{ }} protect against all XSS?

Blade {{ }} protects against XSS in HTML context by encoding <, >, &, ", and '. However, it does not protect when outputting into JavaScript, CSS, or URL contexts. Use @json for JavaScript, and always validate/sanitize URLs before outputting them in href attributes.

Is Livewire vulnerable to XSS?

Livewire uses Blade rendering, so the same rules apply. Properties rendered with {{ }} are safe. If you use wire:model on contenteditable elements or render HTML with {!! !!}, you need to sanitize the content. Livewire's Alpine.js directives should also be checked for user input rendering.

What about XSS in Markdown rendering?

Markdown parsers like league/commonmark can be configured to strip HTML. Use the disallowed_raw_html extension or pipe output through HTMLPurifier. Never trust that Markdown alone prevents XSS, as most parsers allow inline HTML by default.

Can Vue.js or React frontends be vulnerable to XSS even when using Laravel?

Yes. Vue's v-html directive and React's dangerouslySetInnerHTML both bypass framework escaping and can execute injected scripts. Additionally, DOM-based XSS can occur when JavaScript reads from location.hash, document.referrer, or URL parameters and writes the value to the DOM without encoding. Treat these frontend APIs with the same care as Blade's {!! !!}.

How do I safely output user-supplied URLs in href attributes?

Blade's {{ $url }} HTML-encodes the value but doesn't prevent javascript: URLs, which browsers execute when clicked. Always validate that URLs use a safe scheme before outputting them: $safeUrl = Str::startsWith($url, ['http://', 'https://']) ? $url : '#'; Then use {{ $safeUrl }} in your template. Never output unvalidated URLs in href, src, or action attributes.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.