Skip to content

SVG Sandboxing#567

Draft
KManolov3 wants to merge 18 commits into
developfrom
feature/svgs-iframing
Draft

SVG Sandboxing#567
KManolov3 wants to merge 18 commits into
developfrom
feature/svgs-iframing

Conversation

@KManolov3

Copy link
Copy Markdown
Contributor

Resolves

https://scratchfoundation.atlassian.net/browse/UEPR-231

Proposed Changes

  • Load SVGs into a sandboxed iframe for measurement vs directly into the DOM.
  • Introduce a new function for removing malicious content from SVGs - canonicalizeSvgText
  • TODO: Route all svg loads through canonicalizeSvgText - at the point of loadVector_

Reason for Changes

Currently we attempt to sanitize SVGs, but the approach is piecemeal. The biggest security issue in the current state is that we load SVGs directly into the DOM, which is an inherently unsafe operation.

Test Coverage

Added tests for sandboxing, canonicalization and measuring SVGs in a sandboxed environment.

@KManolov3 KManolov3 requested review from adzhindzhi and cwillisf May 11, 2026 15:35
@KManolov3 KManolov3 requested a review from a team as a code owner May 11, 2026 15:35
@KManolov3 KManolov3 marked this pull request as draft May 11, 2026 15:37
@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Test Results

   10 files  1 167 suites   14m 28s ⏱️
3 064 tests 3 056 ✅  8 💤 0 ❌
6 738 runs  6 699 ✅ 39 💤 0 ❌

Results for commit d026072.

♻️ This comment has been updated with latest results.

@rubiidev18alt

Copy link
Copy Markdown

Please keep the cool css theming via svg 🙏

Comment thread packages/scratch-render/src/RenderWebGL.js
Comment thread packages/scratch-svg-renderer/src/sandbox/index.js
Comment on lines +85 to +90
if (REMOVE_ELEMENTS.has(node.name)) {
parentNode.children = parentNode.children.filter(
child => child !== node
);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would the current logic remove tags like <svg:script> or <svg:style> (or other namespaced tags), which I believe are also valid SVG elements? It seems like they would currently survive the canonicalization step, meaning their CSS rules (including external url() references) could persist.

This should be fine for now since sanitizeSvgText is still used, and as far as I can tell DOMPurify strips those tags (and leaves the content as a text node, which is still not ideal). But if we're planning to deprecate that at some point, or if we want canonicalize-svg to handle sanitization more comprehensively, it might be worth addressing here as well.

Comment on lines +121 to +123
if (val && !isInternalRef(val.replace(/\s/g, ''))) {
delete node.attributes[attr];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I noticed is that, as far as I can tell, the current canonicalization allows data: URIs on all elements, whereas the old sanitization logic only allowed them on image tags. I'm not sure whether this could actually be abused in practice, especially considering the current pipeline, but it still might be worth mentioning.

// ── Element / attribute classification ─────────────────────────────────────

/** Elements removed entirely (children discarded). */
const REMOVE_ELEMENTS = new Set([

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I approve of the work that has been done so far on the PR!

The main concern is to not be overly restrictive or not restrictive enough when it comes to removing elements and attributes here. You seem to have taken care of all cases that I could think of. The most important thing here has to be to double-check with a handful of examples right before deploying the code that we catch all of the SVGs that have been known to cause trouble so far and also not restrict the friendly ones (which, by looking at the current code, shouldn't be a thing).

Minor nitpick: consider the <discard> element, which might remove some other elements in the SVG (doesn't seem like the end of the world but might consider it while we're at it)

Comment on lines +16 to +19
paperSandboxPromise = import(
/* webpackChunkName: "paper-source" */
'@scratch/paper/dist/paper-full.min.js?source'
).then(module => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's important to note that loading this lazily means we'd need to update scratch-www's webpack configuration to copy the chunk from the package's dist folder in node_modules into the js folder it serves chunks from. It would also mean any other consumer of scratch-editor would have to do the same.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants