Stensul: Building an XSS Exception System Without Opening the Door to Attacks

The Problem

Stensul is a multi-tenant email creation platform, so XSS protection wasn’t optional: we ran a dedicated XSS detector service plus filters on both frontend and backend, a solid defense-in-depth setup. The friction came from the fact that legitimate usage and attack patterns often look identical at the parser level. A marketer pasting @import url(‘https://fonts.googleapis.com/…’) to load a Google Font triggers the same rule as a real CSS import-based attack. Same with tracking pixels, custom <style> blocks in proprietary CSS fields, or HTML snippets in module code. The filters didn’t care about intent, they blocked everything, and false positives were piling up in the #xss-production-alerts Slack channel for clients who were technically doing nothing wrong.

How I solved it

I built a per-tenant exception system stored as a document in each client’s settings collection, not as global config. The default behavior stayed strict, no XSS insertions allowed, full stop. For clients with a legitimate need, exceptions were defined at three levels of granularity: the entity (Library, Module, Campaign, Snippet…), the specific field inside that entity (configPropietaryCss, body_html, data.text…), and the exact rule to skip (css.import, linkTag, frames…). That way, allowing a client to use @import for fonts inside their library’s custom head didn’t silently open the door to <iframe> injections in their campaign body.

The workflow I designed around it was just as important as the data model. Every alert went to Slack, the encoded payload was pulled from Kibana logs, decoded (gzip + base64), classified against a known attack table, and only then mapped to an entity/field exception. New exceptions were always merged into the existing object, never replaced, and tested against the original “attack” before shipping, to confirm we were skipping exactly what we intended and nothing more.

What I took from it

It was the principle of least privilege applied to XSS: nobody gets exceptions by default, you earn them explicitly, scoped to the smallest surface that solves the problem. The lesson? Good security isn’t about saying no to everything, it’s about saying yes only when you’ve thought it through, written it down, and made it auditable.