Cross-site Scripting (XSS) - Stored in inventree/inventree
Description
Cross-site Scripting (XSS) - Stored in GitHub repository inventree/inventree prior to 0.8.3.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Stored XSS in InvenTree prior to 0.8.3 allows attackers to inject arbitrary scripts via unsanitized SVG attachments.
Vulnerability
Description
CVE-2022-3355 is a stored cross-site scripting (XSS) vulnerability affecting InvenTree, an open-source inventory management system. It exists in versions prior to 0.8.3 and stems from insufficient sanitization of SVG file attachments. The official description confirms this as a Stored XSS issue [1].
Exploitation
An attacker can exploit this by uploading a specially crafted SVG file as an attachment to any part of the application (e.g., parts, stock items). The lack of input validation on SVG content allows embedding arbitrary JavaScript. When other users view the attachment, the malicious script executes in their browser context without requiring additional privileges [1][2].
Impact
Successful exploitation enables script execution in the victim's session, potentially leading to session theft, data exfiltration, or other malicious actions. The attack surface is broad since attachments are a common feature in inventory management workflows.
Mitigation
The vulnerability is fixed in InvenTree version 0.8.3, which includes a commit that adds SVG sanitization using the bleach library and an allowlist of elements and attributes [2][4]. Users should upgrade to 0.8.3 or later to mitigate the risk.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
inventreePyPI | < 0.8.3 | 0.8.3 |
Affected products
1- Range: unspecified
Patches
15a08ef908dd5Add sanitation for SVG attachments (#3701)
3 files changed · +95 −0
InvenTree/InvenTree/models.py+10 −0 modified@@ -4,6 +4,7 @@ import os import re from datetime import datetime +from io import BytesIO from django.conf import settings from django.contrib.auth import get_user_model @@ -24,6 +25,7 @@ import InvenTree.helpers from common.models import InvenTreeSetting from InvenTree.fields import InvenTreeURLField +from InvenTree.sanitizer import sanitize_svg logger = logging.getLogger('inventree') @@ -383,8 +385,16 @@ def save(self, *args, **kwargs): 'link': _('Missing external link'), }) + if self.attachment.name.lower().endswith('.svg'): + self.attachment.file.file = self.clean_svg(self.attachment) + super().save(*args, **kwargs) + def clean_svg(self, field): + """Sanitize SVG file before saving.""" + cleaned = sanitize_svg(field.file.read()) + return BytesIO(bytes(cleaned, 'utf8')) + def __str__(self): """Human name for attachment.""" if self.attachment is not None:
InvenTree/InvenTree/sanitizer.py+67 −0 added@@ -0,0 +1,67 @@ +"""Functions to sanitize user input files.""" +from bleach import clean +from bleach.css_sanitizer import CSSSanitizer + +ALLOWED_ELEMENTS_SVG = [ + 'a', 'animate', 'animateColor', 'animateMotion', + 'animateTransform', 'circle', 'defs', 'desc', 'ellipse', 'font-face', + 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern', + 'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph', + 'mpath', 'path', 'polygon', 'polyline', 'radialGradient', 'rect', + 'set', 'stop', 'svg', 'switch', 'text', 'title', 'tspan', 'use' +] + +ALLOWED_ATTRIBUTES_SVG = [ + 'accent-height', 'accumulate', 'additive', 'alphabetic', + 'arabic-form', 'ascent', 'attributeName', 'attributeType', + 'baseProfile', 'bbox', 'begin', 'by', 'calcMode', 'cap-height', + 'class', 'color', 'color-rendering', 'content', 'cx', 'cy', 'd', 'dx', + 'dy', 'descent', 'display', 'dur', 'end', 'fill', 'fill-opacity', + 'fill-rule', 'font-family', 'font-size', 'font-stretch', 'font-style', + 'font-variant', 'font-weight', 'from', 'fx', 'fy', 'g1', 'g2', + 'glyph-name', 'gradientUnits', 'hanging', 'height', 'horiz-adv-x', + 'horiz-origin-x', 'id', 'ideographic', 'k', 'keyPoints', + 'keySplines', 'keyTimes', 'lang', 'marker-end', 'marker-mid', + 'marker-start', 'markerHeight', 'markerUnits', 'markerWidth', + 'mathematical', 'max', 'min', 'name', 'offset', 'opacity', 'orient', + 'origin', 'overline-position', 'overline-thickness', 'panose-1', + 'path', 'pathLength', 'points', 'preserveAspectRatio', 'r', 'refX', + 'refY', 'repeatCount', 'repeatDur', 'requiredExtensions', + 'requiredFeatures', 'restart', 'rotate', 'rx', 'ry', 'slope', + 'stemh', 'stemv', 'stop-color', 'stop-opacity', + 'strikethrough-position', 'strikethrough-thickness', 'stroke', + 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', + 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', + 'stroke-width', 'systemLanguage', 'target', 'text-anchor', 'to', + 'transform', 'type', 'u1', 'u2', 'underline-position', + 'underline-thickness', 'unicode', 'unicode-range', 'units-per-em', + 'values', 'version', 'viewBox', 'visibility', 'width', 'widths', 'x', + 'x-height', 'x1', 'x2', 'xlink:actuate', 'xlink:arcrole', + 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', + 'xlink:type', 'xml:base', 'xml:lang', 'xml:space', 'xmlns', + 'xmlns:xlink', 'y', 'y1', 'y2', 'zoomAndPan', 'style' +] + + +def sanitize_svg(file_data: str, strip: bool = True, elements: str = ALLOWED_ELEMENTS_SVG, attributes: str = ALLOWED_ATTRIBUTES_SVG) -> str: + """Sanatize a SVG file. + + Args: + file_data (str): SVG as string. + strip (bool, optional): Should invalid elements get removed. Defaults to True. + elements (str, optional): Allowed elements. Defaults to ALLOWED_ELEMENTS_SVG. + attributes (str, optional): Allowed attributes. Defaults to ALLOWED_ATTRIBUTES_SVG. + + Returns: + str: Sanitzied SVG file. + """ + + cleaned = clean( + file_data, + tags=elements, + attributes=attributes, + strip=strip, + strip_comments=strip, + css_sanitizer=CSSSanitizer() + ) + return cleaned
InvenTree/InvenTree/tests.py+18 −0 modified@@ -23,6 +23,7 @@ import InvenTree.tasks from common.models import InvenTreeSetting from common.settings import currency_codes +from InvenTree.sanitizer import sanitize_svg from part.models import Part, PartCategory from stock.models import StockItem, StockLocation @@ -878,3 +879,20 @@ def test_bacode_hash(self): for barcode, hash in hashing_tests.items(): self.assertEqual(InvenTree.helpers.hash_barcode(barcode), hash) + + +class SanitizerTest(TestCase): + """Simple tests for sanitizer functions.""" + + def test_svg_sanitizer(self): + """Test that SVGs are sanitized acordingly.""" + valid_string = """<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2" height="400" width="400">{0} + <path id="path1" d="m -151.78571,359.62883 v 112.76373 l 97.068507,-56.04253 V 303.14815 Z" style="fill:#ddbc91;"></path> + </svg>""" + dangerous_string = valid_string.format('<script>alert();</script>') + + # Test that valid string + self.assertEqual(valid_string, sanitize_svg(valid_string)) + + # Test that invalid string is cleanded + self.assertNotEqual(dangerous_string, sanitize_svg(dangerous_string))
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-62g7-fpv9-v95fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-3355ghsaADVISORY
- github.com/inventree/InvenTree/releases/tag/0.8.3ghsaWEB
- github.com/inventree/inventree/commit/5a08ef908dd5344b4433436a4679d122f7f99e41ghsax_refsource_MISCWEB
- huntr.dev/bounties/4b7fb92c-f06b-4bbf-82dc-9f013b30b6a6ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.