Skip to content

fix: Security removal dependency svg-prep#3236

Merged
mtrezza merged 2 commits intoparse-community:alphafrom
mtrezza:fix/svg
Feb 20, 2026
Merged

fix: Security removal dependency svg-prep#3236
mtrezza merged 2 commits intoparse-community:alphafrom
mtrezza:fix/svg

Conversation

@mtrezza
Copy link
Member

@mtrezza mtrezza commented Feb 20, 2026

Pull Request

Issue

Security removal dependency svg-prep

Summary by CodeRabbit

  • Chores
    • Removed a development-only SVG preprocessing tool from the build setup.
    • Inlined and simplified SVG sprite generation within the build pipeline, including deterministic file ordering and an early-exit when no sources are configured.
    • Consolidated build dependencies to streamline local development and build maintenance.

@parse-github-assistant
Copy link

🚀 Thanks for opening this pull request!

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Removed the svg-prep devDependency from package.json and inlined its sprite-building logic into webpack/plugins/svg-prep.js via a new buildSvgSprite function that reads, sanitizes, and assembles SVGs into sprites.svg; plugin now sorts files and returns early when no source is configured.

Changes

Cohort / File(s) Summary
Build Configuration
package.json
Removed devDependency "svg-prep": "1.0.4".
Webpack Plugin
webpack/plugins/svg-prep.js
Replaced external svg-prep usage with an inlined buildSvgSprite(files) implementation: reads SVGs, extracts viewBox and inner content, sanitizes attributes/elements, wraps into <symbol> entries and outputs a sprites.svg; sorts SVG file list before processing; changed early exit to a simple return when no source configured; export signature unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. While it includes the template structure and security context, it lacks details in the 'Issue' and 'Approach' sections, missing explanation of the security concern and how the changes address it. Fill in the 'Issue' section with specific security vulnerability details, complete the 'Approach' section explaining the refactoring/inlining strategy, and indicate which tasks (tests/documentation) were completed.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main change: removing the svg-prep dependency for security reasons, which aligns with the changes shown in package.json and the refactored svg-prep.js plugin.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Feb 20, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
webpack/plugins/svg-prep.js (1)

19-23: Synchronous file I/O inside an async tapPromise handler blocks the event loop.

Both fs.readdirSync and fs.readFileSync are synchronous. Inside a tapPromise (async) hook, the idiomatic approach is fs.promises.readdir / fs.promises.readFile so Webpack can overlap I/O with other work. For a small icon set at build time the impact is negligible, but it does defeat the async contract.

♻️ Proposed async refactor
-const fs = require('fs');
+const fs = require('fs');
+const fsPromises = require('fs').promises;

 async function buildSvgSprite(files) {
   const symbols = await Promise.all(files.map(async file => {
     const name = path.basename(file, '.svg');
-    const svg = fs.readFileSync(file, 'utf-8');
+    const svg = await fsPromises.readFile(file, 'utf-8');
     // ... rest unchanged
   }));
   return [
     '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
     '<svg id="sprites" xmlns="http://www.w3.org/2000/svg" style="display:none">',
     symbols.join('\n'),
     '</svg>',
   ].join('\n');
 }

In SvgPrepPlugin.prototype.apply:

-        const files = fs
-          .readdirSync(this.options.source)
+        const files = (await fsPromises.readdir(this.options.source))
           .filter(name => name.endsWith('.svg'))
           .sort()
           .map(name => path.join(this.options.source, name));
 
-        const sprited = buildSvgSprite(files);
+        const sprited = await buildSvgSprite(files);

Also applies to: 81-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webpack/plugins/svg-prep.js` around lines 19 - 23, The buildSvgSprite
function and the SvgPrepPlugin.prototype.apply tapPromise handler currently use
synchronous I/O (fs.readdirSync, fs.readFileSync) which blocks the event loop;
change these to the Promise-based APIs (fs.promises.readdir and
fs.promises.readFile) and make buildSvgSprite async so it returns a Promise of
symbols, then await it inside the tapPromise handler (or return the Promise
directly from the tapPromise callback). Update references to path.basename and
any synchronous parsing to work with the async flow and ensure error handling
uses try/catch inside the async function so the tapPromise rejects on error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webpack/plugins/svg-prep.js`:
- Around line 34-45: The sanitization pipeline building the `cleaned` string
from `inner` must explicitly remove <script> tags and their content, strip all
event-handler attributes matching /^on[a-z]+/ (both double- and single-quoted
forms), and neutralize href/xlink:href attributes that contain javascript: URIs;
update the chain of .replace(...) calls that produce `cleaned` to include
regexes that remove /<script[\s\S]*?<\/script>/gi, attributes like
/\s+on[a-z]+="[^"]*"/gi and /\s+on[a-z]+='[^']*'/gi, and href/xlink:href
patterns that match javascript: URIs (both quoted variants) so SVG sprites
cannot carry script or event XSS vectors while preserving the existing
style/defs/title/desc/comment removal behavior.
- Line 47: The symbol id is used verbatim from name and can produce malformed
XML/invalid NCName values; create and call a sanitizeSymbolId(name) helper (or
inline logic) before embedding it in the template: 1) escape XML attribute
characters (&, <, >, ", ') for the id attribute value, 2) normalize/replace
invalid NCName characters (whitespace, colons, slashes, punctuation) with safe
characters (e.g. hyphen), and 3) ensure the id does not start with a digit or
other invalid starting char (prefix with a safe string like "svg-" when needed);
then replace the existing id="${name}" usage with id="${sanitizeSymbolId(name)}"
and keep the cleaned content insertion as-is.

---

Nitpick comments:
In `@webpack/plugins/svg-prep.js`:
- Around line 19-23: The buildSvgSprite function and the
SvgPrepPlugin.prototype.apply tapPromise handler currently use synchronous I/O
(fs.readdirSync, fs.readFileSync) which blocks the event loop; change these to
the Promise-based APIs (fs.promises.readdir and fs.promises.readFile) and make
buildSvgSprite async so it returns a Promise of symbols, then await it inside
the tapPromise handler (or return the Promise directly from the tapPromise
callback). Update references to path.basename and any synchronous parsing to
work with the async flow and ensure error handling uses try/catch inside the
async function so the tapPromise rejects on error.

@mtrezza mtrezza merged commit abb08c6 into parse-community:alpha Feb 20, 2026
10 of 11 checks passed
@mtrezza mtrezza deleted the fix/svg branch February 20, 2026 01:43
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
webpack/plugins/svg-prep.js (1)

25-26: viewBox extraction silently fails for single-quoted attributes.

The regex only matches double-quoted viewBox values. Any icon SVG using viewBox='...' (single-quoted, which is valid per the XML spec) will silently fall back to 0 0 100 100 and may render distorted.

♻️ Proposed fix to handle both quote styles
-    const viewBoxMatch = svg.match(/viewBox="([^"]*)"/);
+    const viewBoxMatch = svg.match(/viewBox=["']([^"']*)["']/);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webpack/plugins/svg-prep.js` around lines 25 - 26, The viewBox extraction in
svg-prep.js only matches double-quoted attributes and falls back incorrectly;
update the regex used with the svg string (the code creating viewBoxMatch and
viewBox) to accept either single or double quotes (e.g., match
viewBox=(["'])(.*?)\1), then set viewBox = viewBoxMatch ? viewBoxMatch[2] : '0 0
100 100' so single-quoted attributes are correctly captured by the existing
viewBoxMatch/viewBox logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webpack/plugins/svg-prep.js`:
- Around line 34-50: The sanitization pipeline building the cleaned string
(variable cleaned in svg-prep.js) currently strips scripts, event handlers,
hrefs, etc., but misses <foreignObject> elements; update the chain of .replace
calls that produce cleaned to also remove any <foreignObject>...</foreignObject>
blocks (case-insensitive) by adding a regex replacement for
/<foreignObject[\s\S]*?<\/foreignObject>/gi so embedded HTML/iframes inside SVGs
are stripped before writing sprites.svg.

---

Duplicate comments:
In `@webpack/plugins/svg-prep.js`:
- Line 52: The symbol id is generated directly from filename (variable name) and
can produce invalid XML/NCName values; implement a sanitize function (e.g.,
sanitizeSvgId(name)) used before interpolation to: 1) remove or replace
XML-special characters (&, <, >, ", ') and also remove colons, whitespace and
other non-name characters (replace them with hyphens), 2) ensure the result
starts with a letter or underscore (prefix with '_' or 'id-' if it starts with a
digit or other invalid start), and 3) collapse repeated hyphens and trim
boundary hyphens so the final id is a valid NCName; then use sanitizeSvgId(name)
instead of name in the template that returns the <symbol> string.

---

Nitpick comments:
In `@webpack/plugins/svg-prep.js`:
- Around line 25-26: The viewBox extraction in svg-prep.js only matches
double-quoted attributes and falls back incorrectly; update the regex used with
the svg string (the code creating viewBoxMatch and viewBox) to accept either
single or double quotes (e.g., match viewBox=(["'])(.*?)\1), then set viewBox =
viewBoxMatch ? viewBoxMatch[2] : '0 0 100 100' so single-quoted attributes are
correctly captured by the existing viewBoxMatch/viewBox logic.

Comment on lines +34 to +50
const cleaned = inner
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<defs[\s\S]*?<\/defs>/gi, '')
.replace(/<title[\s\S]*?<\/title>/gi, '')
.replace(/<desc[\s\S]*?<\/desc>/gi, '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<!--[\s\S]*?-->/g, '')
.replace(/\s+id="[^"]*"/g, '')
.replace(/\s+fill="[^"]*"/g, '')
.replace(/\s+class="[^"]*"/g, '')
.replace(/\s+style="[^"]*"/g, '')
.replace(/\s+stroke="[^"]*"/g, '')
.replace(/\s+stroke-[a-z]+="[^"]*"/g, '')
.replace(/\s+on[a-zA-Z]+="[^"]*"/g, '')
.replace(/\s+on[a-zA-Z]+='[^']*'/g, '')
.replace(/\s+href="[^"]*"/g, '')
.replace(/\s+xlink:href="[^"]*"/g, '');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

<foreignObject> elements are not stripped.

All previously flagged XSS vectors (<script>, on* attributes, href/xlink:href) are now removed. However, <foreignObject> — which allows embedding arbitrary HTML content inside SVG — is still passed through. Even though descendant on* attributes would be caught by the attribute regexes, the element itself can carry HTML <iframe>, navigation anchors, and other non-script vectors that survive into sprites.svg.

🔒 Proposed addition to strip <foreignObject>
     const cleaned = inner
       .replace(/<style[\s\S]*?<\/style>/gi, '')
       .replace(/<defs[\s\S]*?<\/defs>/gi, '')
       .replace(/<title[\s\S]*?<\/title>/gi, '')
       .replace(/<desc[\s\S]*?<\/desc>/gi, '')
       .replace(/<script[\s\S]*?<\/script>/gi, '')
+      .replace(/<foreignObject[\s\S]*?<\/foreignObject>/gi, '')
       .replace(/<!--[\s\S]*?-->/g, '')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webpack/plugins/svg-prep.js` around lines 34 - 50, The sanitization pipeline
building the cleaned string (variable cleaned in svg-prep.js) currently strips
scripts, event handlers, hrefs, etc., but misses <foreignObject> elements;
update the chain of .replace calls that produce cleaned to also remove any
<foreignObject>...</foreignObject> blocks (case-insensitive) by adding a regex
replacement for /<foreignObject[\s\S]*?<\/foreignObject>/gi so embedded
HTML/iframes inside SVGs are stripped before writing sprites.svg.

parseplatformorg pushed a commit that referenced this pull request Feb 20, 2026
## [9.0.1-alpha.7](9.0.1-alpha.6...9.0.1-alpha.7) (2026-02-20)

### Bug Fixes

* Security removal dependency svg-prep ([#3236](#3236)) ([abb08c6](abb08c6))
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 9.0.1-alpha.7

@parseplatformorg parseplatformorg added the state:released-alpha Released as alpha version label Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:released-alpha Released as alpha version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants