Security
How UX Driver escapes popover content, and how to opt out safely.
Driver.js writes popover content and button labels with innerHTML. Left alone, that makes every title and
description an HTML injection sink. UX Driver escapes plain strings before serializing them, so the default
path is safe even when the text comes from the database.
Escaped by default
| Object | Escaped options |
|---|---|
| Tour | title, description, progressText, nextBtnText, prevBtnText, doneBtnText |
| Step | title, description, progressText, nextBtnText, prevBtnText, doneBtnText |
| Highlight | title, description, and the Step options above |
| Hints | buttonText |
| Hint | title, description |
The label options exist at both levels — a Tour sets them for the whole walkthrough, a Step overrides them for itself — and both are escaped identically.
Component
<twig:Driver:Step title="<b>Billing</b>" description="<script>alert(1)</script>" />Twig builder
{% set tour = create_tour('billing-tour')
.addStep('.billing', '<b>Billing</b>', '<script>alert(1)</script>') %}PHP builder
$tour = $this->tourBuilder->create('billing-tour')
->addStep('.billing', '<b>Billing</b>', '<script>alert(1)</script>');That popover shows the literal characters. No bold text, no script. Escaping happens in the model classes, so it is identical in all three modes.
Trusted HTML
When markup is the point — a <strong>, a <code>, a line break — ux_driver_html() marks a string as
trusted and skips escaping:
Component
<twig:Driver:Step :title="ux_driver_html('<strong>Billing</strong>')" />Twig builder
{% set tour = create_tour('billing-tour')
.addStep('.billing', ux_driver_html('<strong>Billing</strong>')) %}PHP builder
use Twig\Markup;
$tour = $this->tourBuilder->create('billing-tour')
->addStep('.billing', new Markup('<strong>Billing</strong>', 'UTF-8'));ux_driver_html() is only the Twig door to the same thing: any Twig\Markup instance skips escaping.
Sanitizing rich text
If popover content genuinely has to come from user-authored rich text, sanitize it first with
symfony/html-sanitizer and mark the result
trusted:
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
use Twig\Markup;
public function __construct(private readonly HtmlSanitizerInterface $htmlSanitizer)
{
}
public function step(string $userHtml): Markup
{
return new Markup($this->htmlSanitizer->sanitize($userHtml), 'UTF-8');
}
Configure the sanitizer’s allowed elements to the smallest set the popover needs — inline formatting is
usually enough, and a popover has no business rendering <iframe> or <form>. The policy is the
application’s to choose; the bundle only decides whether to escape.
Related
- Styling and localization — the label options above, and how to translate them.
- Highlights — same escaping rules, one Step.