The Silent Sabotage of Mermaid Diagrams
Forem, the open-source platform powering DEV.to, recently integrated Mermaid diagram support. This allows developers to embed dynamic diagrams directly within posts, transforming code blocks into visual representations. However, a critical security alert with a seemingly straightforward one-line fix nearly caused silent data corruption. Applying this fix would have broken the labels on four out of five common diagram types, leading to incorrect visualizations without any crashes or test failures.
The core of the issue lies in how Forem renders Mermaid diagrams. Previously, fenced mermaid code blocks were displayed as plain, highlighted source code. The new implementation allows these blocks to be parsed and rendered as actual diagrams. For example, a simple flowchart that would previously appear as text now renders as an interactive visual:
flowchart TD
A[CodeQL flags innerHTML] --> B[Add DOMPurify]
B --> C{Measure the output}
C -->|assumed| D[Ship it]
C -->|actually| E[Labels are gone]
E --> F[Find the real bug]
The Vulnerability and the 'Obvious' Fix
The security alert flagged the use of innerHTML within the Mermaid rendering pipeline. This is a common vector for Cross-Site Scripting (XSS) attacks, where malicious scripts can be injected through user-generated content and executed in the browser. The standard, and often recommended, solution for sanitizing HTML content before rendering is to use a library like DOMPurify. In this case, the proposed fix was to wrap the problematic innerHTML call with DOMPurify. This seemed like a robust, one-line solution to eliminate the XSS risk.
The code change looked something like this:
// Original, vulnerable code
container.innerHTML = renderedMermaid;
// Proposed 'fix'
container.innerHTML = DOMPurify.sanitize(renderedMermaid);
This approach is generally sound. DOMPurify is designed to strip out potentially harmful HTML and JavaScript, leaving only safe content. The assumption was that the output of the Mermaid renderer, when sanitized, would remain functionally identical, merely stripped of any malicious payloads.
The Unforeseen Consequence: Label Annihilation
The problem emerged when testing the fix against various Mermaid diagram types. It turned out that DOMPurify, in its zealous effort to sanitize, was also stripping out legitimate parts of the rendered Mermaid SVG output. Specifically, it was removing the SVG elements responsible for rendering text labels within the diagrams. This meant that while the diagrams would still render, the crucial textual information—the nodes, connectors, and titles—would be absent. The diagrams would become visually incomplete, rendering them useless for conveying information.
This effect was not uniform. The issue impacted four of the five most commonly used diagram types in Mermaid. This selective breakage is particularly insidious because it wouldn't trigger any alarms. Unit tests focused on the overall rendering process might pass because an SVG element was still produced. End-to-end tests might also pass if they only checked for the presence of an SVG, not its content integrity. Users would simply see broken diagrams, likely leading to confusion and a loss of trust in the platform's features.
Tracing the Bug: A Tale of SVG and Sanitization
The root cause was traced back to how DOMPurify handles SVG elements and attributes. While DOMPurify is excellent at preventing script execution, its default configuration can be overly aggressive when dealing with complex SVG structures. In the case of Mermaid's SVG output, certain text elements or attributes that were essential for label rendering were being interpreted as potentially unsafe by DOMPurify's default rules. This led to their removal during the sanitization process.
The developer behind the fix, Abdellatif, discovered this by meticulously measuring the output. Instead of simply assuming the sanitized output was correct, they compared the original, unsanitized SVG with the output after DOMPurify had processed it. This comparison revealed the missing text elements. The debugging process involved understanding both the Mermaid rendering internals and the specific sanitization rules of DOMPurify.
The solution required a more nuanced approach than a simple one-line fix. It involved configuring DOMPurify to explicitly allow the specific SVG elements and attributes used by Mermaid for its labels. This is a delicate balancing act: allowing necessary components for functionality while still preventing malicious code injection. It requires a deeper understanding of the specific sanitization needs of the content being rendered.
Rethinking Security Fixes
This incident underscores a critical point in software development: security fixes are not always simple drop-in replacements. An obvious security vulnerability and its seemingly obvious fix can have cascading, unforeseen consequences on application functionality. Developers must not only understand the security implications but also the functional impact of applying such fixes.
The scenario highlights the importance of thorough testing and validation, especially when introducing security measures that modify how content is processed or displayed. It’s not enough for a feature to
