Security concerns that start in markup: cross-site scripting (XSS) vectors, safe linking with rel, and sandboxed iframes. HTML is not executable logic on its own, but it is the delivery mechanism for scripts, URLs, and embedded content. Treat untrusted input as hostile.
XSS and untrusted HTML
XSS happens when attacker-controlled strings become live HTML or JavaScript in the browser. Classic vectors include unescaped user comments, search query reflection, and CMS fields rendered raw. The fix is not ‘be careful’ – it is never inserting untrusted data into HTML without encoding.
In PHP or templating engines, escape on output according to context:
- Text content: HTML-encode
<,>,&, quotes - Attribute values: encode for attributes (including quotes)
- URLs: validate scheme (block
javascript:) and encode - JavaScript strings: never build JS by concatenating user input
<!-- Bad: raw user input -->
<p>Hello, <?php echo $_GET['name']; ?></p>
<!-- Good: escaped text -->
<p>Hello, <?php echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8'); ?></p>
Content Security Policy (CSP) is set in HTTP headers, not HTML, but markup habits must align with it. Inline onclick handlers and inline <script> blocks break strict CSP unless you nonce them. Prefer external scripts and event listeners added from a trusted file.
Dangerous patterns to avoid
innerHTMLwith unsanitised strings<script>or event handler attributes from CMS ‘custom HTML’ fieldsjavascript:URLs inhreforsrc- Third-party widgets pasted into templates without review
Link security with rel
When a link opens a new browsing context or points off-site, rel attributes reduce abuse:
<a href="https://partner.example/docs"
target="_blank"
rel="noopener noreferrer">
Partner documentation
</a>
<a href="/user/123/profile"
rel="nofollow ugc">
User profile
</a>
noopener stops the new page from reaching window.opener – a tab-nabbing risk. noreferrer also omits the Referer header (with side effects for analytics). nofollow and ugc signal to search engines that you do not vouch for the target; they are not security controls, but they belong in markup for user-generated links.
Sandboxed iframes
Embedding untrusted HTML (ads, user uploads, third-party widgets) belongs in a sandboxed iframe with the minimum permissions required:
<iframe
src="https://embed.example/widget"
title="Booking calendar"
sandbox="allow-scripts allow-same-origin"
referrerpolicy="strict-origin-when-cross-origin"
loading="lazy"
width="400" height="300">
</iframe>
An empty sandbox attribute applies maximum restriction. Each allow-* token relaxes one capability. allow-scripts plus allow-same-origin together can allow escape from the sandbox if the embedded document is same-origin – treat that combination as trusted code only. Prefer srcdoc with escaped static HTML for tiny embeds when you control the content entirely.
Trust boundaries in templates
Mark in your codebase which variables are trusted HTML (rare) versus plain text (almost everything). WordPress functions like esc_html(), esc_attr(), and esc_url() exist for a reason – mirror that discipline in any stack. Security in markup is mostly discipline at the boundary where data becomes tags.

