Skip to content

Stop authors saving scripts in Laravel ​

A post is HTML, and HTML can run code. On a site with one writer that is fine: you write what you want on your own pages. Once other people write too — authors, contributors, shop staff, a translator, or an AI agent working for any of them — one of them can put a <script> or an onerror= into a post. Nothing happens to them. It runs when an administrator opens that post, with the administrator's session. That is a stored XSS, and the usual way it gets in is not an attacker: it is a paragraph pasted from a newsletter tool or another site, carrying a tracker nobody saw.

WordPress has stopped this for twenty years with a filter called kses. Only users who may write unfiltered HTML keep their markup as written; everyone else has it cleaned when they save. A Laravel site that lets several people write usually has nothing of the kind, or a purifier called in one controller and forgotten in the next. kitloom/content-sanitizer puts the rule where every save passes.

Whose HTML is cleaned ​

Everyone's except the holders of Write unfiltered HTML — WordPress's own unfiltered_html capability. As on a single WordPress site, Administrator and Editor hold it; Author, Contributor, Subscriber and any role you made do not. It is a permission like any other on Users → Roles: take it from editors who are freelancers, give it to a role you trust. Content Sanitizer → Settings says at the top who holds it now.

What is cleaned is every HTML text a user writes:

  • a post's or a page's content and excerpt, products and any other type included;
  • a category's, a tag's or any term's description;
  • a WYSIWYG custom field, at any depth of groups, repeaters and flexible content;
  • a translation's content and excerpt.

The rule sits in the core's writers, not in a screen. So the same holds from the Filament admin, the Nova admin, the REST API, a spreadsheet import and an AI agent connected over MCP. Imports, the demo and console commands write as the site itself, and a WordPress export comes over as it was.

What stays and what goes ​

What a writer needs stays: headings, lists, links, quotes, tables, figures and captions, images with their srcset, video and audio, the block editor's markup with its colours and alignments, shortcodes. Text and entities are kept byte for byte: a clean save changes nothing, so revisions show no difference.

What runs goes: <script>, <style>, forms, <object>, event handlers, attributes a script of the site reads to run something (hx-*, x-*, data-bs-*), javascript: and data: addresses however they are hidden, inline styles that load something or lay a box over the page, and frames from any site not on your list. YouTube, Vimeo and Google Maps are on it from the start; a path narrows a host, so www.google.com/maps lets Maps in and nothing else from Google.

The HTML is read by PHP's own HTML5 parser (Dom\HTMLDocument, PHP 8.4), the one a browser follows, against a list written from the HTML standard. No outside library, and no regex guessing where a tag ends.

Nothing is refused ​

A save always goes through, without what was removed, and the person is told what went by name: Removed from what you saved, as your role may not store it: script, onclick. An author who pastes from Word can still save. An AI agent gets the same names in its tool's answer, so it can tell its user why the embed it was asked to add did not stay.

Only what a user changed is cleaned. An author who moves your post to another category, or saves a page with your fields untouched, leaves your markup alone.

What was there before ​

Posts written before the module, or brought over from WordPress, are not touched until someone without the permission saves them. To see what would change, where and whose:

bash
php artisan content-sanitizer:scan
php artisan content-sanitizer:scan --fix   # after a backup

Watch it ​

Two short lessons from the admin, narrated:

Content Sanitizer settings: frames, styles, and who keeps raw HTML
An author pastes a script: what goes, and how they are told

Try it ​

Open a sandbox in one click, with no sign-up: Filament or Nova. Every detail of the module is in the Content Sanitizer guide.

Last updated:

kitloom — modules and themes for Laravel.
Contact · Pricing · Services · License · Refunds · Privacy · Community · YouTube · [email protected]