Skip to content
Open
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 @@ -14,3 +14,8 @@
**Vulnerability:** Several `escapeHtml` implementations used DOM manipulation (`document.createElement('div').innerHTML`) or incomplete string replacement, failing to escape single and double quotes.
**Learning:** Incomplete escaping allows XSS payloads to break out of HTML attributes (e.g., `<input value="${escapeHtml(userInput)}">`).
**Prevention:** Always use `String(text)` cast combined with a comprehensive replace chain for `&`, `<`, `>`, `"`, and `'` (e.g., `&#039;`) in custom string escaping functions.

## 2025-02-14 - Fix Fail Open XSS Vulnerability in Markdown Parsing
**Vulnerability:** Raw HTML strings generated by `marked.parse` for node specifications were directly assigned to `innerHTML` without sanitization. If `marked` or an adversary injects malicious `<script>` tags or attributes, they execute as part of the page content.
**Learning:** `marked.parse` itself does not sanitize HTML output by default. When using libraries for rendering untrusted markdown inside a trusted UI frame, conditionally relying on safe fallbacks or specific libraries isn't sufficient without strict conditional sanitization (e.g. `DOMPurify` if available).
**Prevention:** Always wrap dynamically generated HTML interpolations (like markdown rendering) using `DOMPurify.sanitize(..., { ADD_ATTR: [...] })` before assigning them to the DOM. If the sanitizer library is missing, degrade securely using regex-based HTML escaping `<pre>${str.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</pre>`.
4 changes: 3 additions & 1 deletion site/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2723,8 +2723,10 @@ function renderSpecsPanel() {
);

if (typeof marked !== 'undefined') {
// SECURITY: Conditionally sanitize raw markdown HTML to prevent XSS.
// Falls back to safe text escaping if DOMPurify isn't loaded.
const rendered = marked.parse(processedNote);
html += rendered;
html += window.DOMPurify ? window.DOMPurify.sanitize(rendered, { ADD_ATTR: ['data-note-node', 'data-node'] }) : `<pre>${processedNote.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</pre>`;
} else {
html += `<pre>${processedNote.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</pre>`;
}
Expand Down
Loading