Skip to content

fix: preserve SVG <use> elements and href attributes in sanitization#8520

Merged
mscolnick merged 4 commits intomarimo-team:mainfrom
24f2006299:fix/svg-use-elements
Mar 3, 2026
Merged

fix: preserve SVG <use> elements and href attributes in sanitization#8520
mscolnick merged 4 commits intomarimo-team:mainfrom
24f2006299:fix/svg-use-elements

Conversation

@24f2006299
Copy link
Contributor

Summary

Fixes #8316.

SVGs containing <defs> and <use> elements (common in Matplotlib SVG output) were not rendering correctly when displayed via image/svg+xml MIME type. DOMPurify was stripping <use> elements and their href/xlink:href attributes during sanitization.

Changes

Added ADD_TAGS: ["use"] and ADD_ATTR: ["href", "xlink:href"] to the DOMPurify config in sanitize.ts. This preserves SVG <use> elements and their reference attributes while keeping all other sanitization intact.

javascript: protocol in href attributes is still blocked by DOMPurify at the protocol level, so this does not introduce XSS risk.

Test Plan

Updated sanitize.test.ts:

  • Changed existing test from asserting <use> removal to asserting preservation
  • Added test for the full <defs> + <use> SVG pattern (matching the issue reproduction case)

Closes marimo-team#8316

DOMPurify was stripping <use> elements and their href/xlink:href
attributes during sanitization, breaking SVGs that define shapes in
<defs> and reference them via <use> (common in Matplotlib SVG output).

Add "use" to ADD_TAGS and "href"/"xlink:href" to ADD_ATTR in the
DOMPurify config so these elements survive sanitization.
@vercel
Copy link

vercel bot commented Feb 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Mar 3, 2026 6:39pm

Request Review

@github-actions
Copy link

github-actions bot commented Feb 28, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@24f2006299
Copy link
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@Light2Dark Light2Dark requested review from mscolnick and removed request for Light2Dark March 2, 2026 05:12
@mscolnick mscolnick requested review from Copilot and removed request for manzt and mscolnick March 2, 2026 13:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the frontend HTML/SVG sanitization settings to preserve SVG <use> elements (and their reference attributes) so SVGs that rely on <defs> + <use>—such as common Matplotlib SVG output—render correctly when displayed as image/svg+xml.

Changes:

  • Allowlist the SVG <use> tag in the DOMPurify configuration.
  • Allowlist href and xlink:href attributes to preserve <use> references.
  • Update and extend sanitizer tests to assert <use> preservation and cover a <defs> + <use> pattern.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
frontend/src/plugins/core/sanitize.ts Updates DOMPurify config to preserve SVG <use> and href/xlink:href attributes.
frontend/src/plugins/core/test/sanitize.test.ts Updates tests to expect <use> preservation and adds a <defs> + <use> regression case.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +77 to +80
// Allow SVG <use> elements and their href attributes, which are needed
// for SVGs that reference <defs> (e.g., Matplotlib SVG output).
ADD_TAGS: ["use"],
ADD_ATTR: ["href", "xlink:href"],
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that <use> and href/xlink:href are explicitly allowlisted, we should add a regression test that sanitizeHtml still strips dangerous URI schemes for these attributes on <use> specifically (e.g., href="javascript:..." and xlink:href="javascript:..."). The existing SVG URI tests cover <a>, but not <use>, which is the newly-enabled surface area.

Copilot uses AI. Check for mistakes.
Comment on lines +299 to +303
const html =
'<svg width="60" height="60"><circle cx="30" cy="30" r="30" fill="orange"></circle><defs><circle id="myCircle" cx="0" cy="0" r="10" fill="green"></circle></defs><use href="#myCircle" x="20" y="20"></use></svg>';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"<svg width="60" height="60"><circle cx="30" cy="30" r="30" fill="orange"></circle><defs><circle id="myCircle" cx="0" cy="0" r="10" fill="green"></circle></defs><use href="#myCircle" x="20" y="20"></use></svg>"`,
);
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG string in this test is a single very long line, which makes it difficult to read and update. Consider formatting it as a multiline template literal (or otherwise wrapping it) so future diffs are more reviewable and the intent is clearer.

Suggested change
const html =
'<svg width="60" height="60"><circle cx="30" cy="30" r="30" fill="orange"></circle><defs><circle id="myCircle" cx="0" cy="0" r="10" fill="green"></circle></defs><use href="#myCircle" x="20" y="20"></use></svg>';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"<svg width="60" height="60"><circle cx="30" cy="30" r="30" fill="orange"></circle><defs><circle id="myCircle" cx="0" cy="0" r="10" fill="green"></circle></defs><use href="#myCircle" x="20" y="20"></use></svg>"`,
);
const html = `
<svg width="60" height="60">
<circle cx="30" cy="30" r="30" fill="orange"></circle>
<defs>
<circle id="myCircle" cx="0" cy="0" r="10" fill="green"></circle>
</defs>
<use href="#myCircle" x="20" y="20"></use>
</svg>
`;
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`
"<svg width=\"60\" height=\"60\">
<circle cx=\"30\" cy=\"30\" r=\"30\" fill=\"orange\"></circle>
<defs>
<circle id=\"myCircle\" cx=\"0\" cy=\"0\" r=\"10\" fill=\"green\"></circle>
</defs>
<use href=\"#myCircle\" x=\"20\" y=\"20\"></use>
</svg>"
`);

Copilot uses AI. Check for mistakes.
…lements

Address review feedback:
- Add tests verifying javascript: href and xlink:href are stripped from <use>
- Reformat defs+use SVG test string as array join for readability
@24f2006299 24f2006299 force-pushed the fix/svg-use-elements branch from 2b71611 to 94b135f Compare March 2, 2026 16:30
@Light2Dark Light2Dark added the bug Something isn't working label Mar 2, 2026
mscolnick
mscolnick previously approved these changes Mar 2, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

`"<svg><use></use></svg>"`,
);
});

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new <use> coverage verifies javascript: is stripped, but it doesn’t exercise non-javascript: external references (e.g. https://...#id or data:image/svg+xml,...). Adding a test that asserts external/non-fragment href/xlink:href values are removed (if that’s the intended security posture) would help prevent regressions and clarify the expected behavior for <use>.

Suggested change
test("removes external href from SVG use element", () => {
const html =
'<svg><use href="https://example.com/sprite.svg#icon"></use></svg>';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"<svg><use></use></svg>"`,
);
});
test("removes external xlink:href from SVG use element", () => {
const html =
'<svg><use xlink:href="https://example.com/sprite.svg#icon"></use></svg>';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"<svg><use></use></svg>"`,
);
});
test("removes data URI href from SVG use element", () => {
const html =
'<svg><use href="data:image/svg+xml,%3Csvg%3E%3C/svg%3E"></use></svg>';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"<svg><use></use></svg>"`,
);
});

Copilot uses AI. Check for mistakes.
@mscolnick mscolnick merged commit e90e721 into marimo-team:main Mar 3, 2026
24 checks passed
@github-actions
Copy link

github-actions bot commented Mar 3, 2026

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.20.3-dev94

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SVG <use> elements not rendered when MIME type is image/svg+xml

4 participants