> ## Content Index
> Fetch the complete content index at: https://securinglaravel.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Security Tip: Avoiding XSS with HtmlString
- URL: https://securinglaravel.com/security-tip-avoiding-xss-with-htmlstring/
- Published: 2023-05-01T17:00:27.000Z
- Updated: 2025-01-28T02:18:13.000Z
- Description: [Tip#44] Check out that one simple trick... I mean... This is my favourite way to avoid XSS.
- Author: Stephen Rees-Carter
- Tags: Security Tips, Audits Top 10, XSS

ℹ️

**This is part of my series on the* [**Top 10 Security Issues*](https://securinglaravel.com/tag/audits-top-10/) *discovered during my Laravel Security Audits, as of April 2023\.*   
  
**#1 →* [**Exposed API Keys & Passwords*](https://securinglaravel.com/in-depth-storing-environment-variables/)  
**#2 →* [**Missing Authorisation*](https://securinglaravel.com/security-tip-test-for-missing-authorisation/)  
**#3 →* [**Missing Content Security Policy (CSP)*](https://securinglaravel.com/security-tip-getting-started-with-csp/)  
**#4 →* [**Missing Security Headers*](https://securinglaravel.com/security-tip-security-headers-are/)  
**#5 →* [**Insecure Function Use*](https://securinglaravel.com/in-depth-what-are-insecure-functions/)  
**#6 →* [**Outdated & Vulnerable Dependencies*](https://securinglaravel.com/security-tip-replace-simple-dependencies/)  
***#7 → Cross-Site Scripting (XSS)**  
**#8 →* [**Insufficient Rate Limiting*](https://securinglaravel.com/security-tip-dont-forget-rate-limiting/)  
**#9 →* [**Missing Subresource Integrity (SRI)*](https://securinglaravel.com/security-tip-subresource-integrity/)  
**#10 →* [**Insufficient Input Validation*](https://securinglaravel.com/security-tip-validating-array-inputs/) *&* [**Mass-Assignment Vulnerabilities*](https://securinglaravel.com/in-depth-mass-assignment-vulnerabilities/)

[Cross-Site Scripting (XSS)](https://securinglaravel.com/in-depth-escaping-output-safely) usually occurs in Laravel apps when we use the unescaped blade tags (`{!! ... !!}`), or the raw-html directives in Vue (`v-html`) and Alpine (`x-html`). These tags and directives output the value as-is, without removing any HTML or encoding special characters.

While there are many legitimate reasons to use these unescaped tags - such as using Markdown, WYSIWYG editors, or building complex HTML structures directly in code - they do pose a massive security risk, and possibly in a way you do not expect.

💡

**Note, I'll specifically talk about the Blade tags, but these concepts apply across any escaped/unescaped output handling.*

Consider the following code:

```
<div>
    <h1>{{ $title }}</h1>
    <p>{!! $description !!}</p>
    {!! $images !!}
    <ul>
        @foreach ($items as $item)
            <li>{!! $item->name !!} - {{ $item->price }}</li>
        @endforeach
    </ul>
    {!! $notes !!}
    {!! $buttons !!}
</div>
```

There are 5 unescaped blade tags in this block of code. The reasons for being unescaped can be easily guessed for the following three:

- `$images` → Probably returns image HTML
- `$notes` → Most likely generated from Markdown or a WYSIWYG/HTML editor.
- `$buttons` → Buttons would be HTML, right?

We can also make the assumption for `$description` and `$item->name` including some markdown… right?

**Wrong!**

`$item->name` comes directly from user input and loading it unescaped on the page opens up a massive **Stored XSS vulnerability**! It wasn't supposed to be outputted unescaped, but because the unescaped tags get used so frequently, the developers didn't notice and the vulnerability was introduced.

💡

***Stored XSS** *→ the injected code is stored in the application and returned any time a user visits the vulnerable page.*  
***Reflected XSS** *→ the injected code is passed into the request it’s loaded on, and requires users to visit a specific link or submit a form to trigger it.*

**This is the reason why unescaping tags pose a massive security risk. If you use them frequently, you lose visibility of when they shouldn’t be used.**

## The HtmlString Helper

The way to avoid this risk is to use Laravel’s `Illuminate\Support\HtmlString` helper class any time you need to output generated HTML on the page. It implements the `Illuminate\Contracts\Support\Htmlable` interface, which Laravel checks for in it’s escaping function `e()`.

Simply wrap your HTML inside the class `HtmlString` and the escaping function will return the raw HTML value.

So for our `$notes` variable above, we can do this when we render it in markdown:

```
$notes = new HtmlString(Str::markdown($rawNotes, [
    'html_input' => 'strip',
    'allow_unsafe_links' => false,
]));
```

Or using the Fluent string interface:

```
$notes = Str::of($rawNotes)
    ->markdown([
        'html_input' => 'strip',
        'allow_unsafe_links' => false,
    ])
    ->toHtmlString();
```

Once you wrap all of your safe HTML variables inside `HtmlString`, you can output them using the escaping tags:

```
<div>
    <h1>{{ $title }}</h1>
    <p>{{ $description }}</p>
    {{ $images }}
    <ul>
        @foreach ($items as $item)
            <li>{!! $item->name !!} - {{ $item->price }}</li>
        @endforeach
    </ul>
    {{ $notes }}
    {{ $buttons }}
</div>
```

Suddenly our XSS vector on `$item->name` jumps out at us as the only use of `{!! ... !!}`, and we immediately know it’s suspicious. It won’t take long for you to dig into it, figure out it’s vulnerable to XSS, and fix it. 😎

## Cross-Site Scripting (XSS) Resources

- [Security Tip: Validating HTML & Markdown Input](https://securinglaravel.com/security-tip-validating-html-and)
- [In Depth: Escaping Output Safely](https://securinglaravel.com/in-depth-escaping-output-safely)
- [Security Tip: Safely Rendering JSON in Blade](https://securinglaravel.com/security-tip-safely-rendering-json)
- [In Depth: Content Security Policy](https://securinglaravel.com/in-depth-content-security-policy)
- [Practical Laravel Security course](https://practicallaravelsecurity.com/?ref=securinglaravel.com)

---

***If you found this security tip useful,*** [***subscribe***](#/portal/signup) ***to get weekly*** [***Security Tips***](https://securinglaravel.com/tag/tips/) **straight to your inbox.* Upgrade to a* [*premium subscription*](#/portal/signup) *for exclusive monthly* [*In Depth articles*](https://securinglaravel.com/tag/in-depth/)*, or drop a coin in the* [*tip jar*](#/portal/support) *to show your support.*

*When was the last time you had a penetration test? Book a* [*Laravel Security Audit and Penetration Test*](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fsource=securinglaravel.com)*, or a budget-friendly* [*Security Review*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com)*!* 

*You can also connect with me on* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*, or* [*other socials*](https://pinkary.com/@valorin?ref=securinglaravel.com)*, and check out* [*Practical Laravel Security*](https://practicallaravelsecurity.com/?utm%5Fsource=securinglaravel.com)*, my interactive course designed to boost your Laravel security skills.*