Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@
**Vulnerability:** Found a DOM Clobbering vulnerability in `_layouts/default.html` where `attachRoadmapNodeLinks()` relied on `document.getElementById('roadmap-node-links')` to retrieve a trusted JSON `<script>` block. Also found a Stored XSS vector in `index.html` where `site.data.roadmap_links` was passed to `jsonify` but not escaped.
**Learning:** If a user injects `<div id="roadmap-node-links">{"malicious": "payload"}</div>` into the markdown, `getElementById` fetches it before the actual script tag, hijacking navigation. Meanwhile, `jsonify` outputs raw `<` and `>`, allowing escaping the `<script>` tag.
**Prevention:** Use `document.querySelector('script#roadmap-node-links')` instead of `getElementById` for configuration scripts. Append `| replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026'` to `jsonify` inside script tags.

## 2025-02-27 - DOMPurify Bypass in Regex Content Swapping
**Vulnerability:** Found a critical DOM-based XSS vulnerability in `assets/js/mermaid-config.js` (`sanitizeMermaidSvg`) where an attacker could bypass `DOMPurify` by embedding malicious attributes (e.g., `onload="alert(1)"`) in SVG `foreignObject` tags. The code extracted `foreignObject` tags using a regex, stored their unsanitized attributes, sanitized the rest of the SVG structure via `DOMPurify`, and then blindly re-injected the raw attributes when restoring the `foreignObject` content.
**Learning:** Using regular expressions to temporarily extract and "protect" HTML structures from a sanitizer often introduces critical bypasses because the extracted parts miss the sanitization pass. If those parts contain attacker-controlled attributes, they will be evaluated by the browser when re-injected.
**Prevention:** Always let `DOMPurify` sanitize attributes natively rather than side-stepping it with regular expressions. When you need to protect or swap inner content, attach the raw attributes to a placeholder element so the sanitizer can clean the attributes before re-assembling the final HTML.
9 changes: 5 additions & 4 deletions assets/js/mermaid-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@
ADD_TAGS: ['semantics', 'annotation'],
ADD_ATTR: ['xmlns', 'encoding'],
});
foreignObjects.push({ attrs: attrs, inner: safeInner, id: id });
return '<foreignObject data-fo-placeholder="' + id + '"></foreignObject>';
foreignObjects.push({ inner: safeInner, id: id });
// Put attributes on the placeholder so DOMPurify cleans them natively!
return '<foreignObject data-fo-placeholder="' + id + '"' + attrs + '></foreignObject>';
}
);

Expand All @@ -189,10 +190,10 @@
foreignObjects.forEach(function (fo) {
sanitized = sanitized.replace(
new RegExp(
'<foreignObject[^>]*data-fo-placeholder="' + fo.id + '"[^>]*></foreignObject>',
'(<foreignObject[^>]*data-fo-placeholder=["\']?' + fo.id + '["\']?[^>]*)></foreignObject>',
'i'
),
'<foreignObject' + fo.attrs + '>' + fo.inner + '</foreignObject>'
'$1>' + fo.inner + '</foreignObject>'
);
});

Expand Down