Skip to main content

svg-sanitize — restrictive, visual-only SVG sanitizer (prototype)

svg-sanitize rebuilds untrusted SVG as static vector graphics only. It does not simply search and replace script tags. It parses XML with defusedxml, checks resource limits, and constructs an entirely new XML tree from a small, explicit allowlist of elements, attributes, and valid values.

Prototype, not a formally verified security boundary. Review, fuzz and test against your target SVG renderers before using this in a security-sensitive production system.

Why svg-sanitize?

The svg-sanitize project started following security reviews conducted by Jeroen Pinoy, which highlighted the potential security risks associated with handling untrusted SVG files. This initial prototype was done during the hack.lu 2026 hackathon after the hot ones interview ;-)

Although SVG is primarily an image format, it can contain active content, including JavaScript, event handlers, external resource references, and other potentially malicious elements. This makes SVG a potential attack vector, particularly when displaying user-supplied images in web applications.

svg-sanitize is a lightweight Python library designed to transform untrusted SVG files into passive, visual-only SVG documents. Instead of attempting to identify every possible malicious payload, the library follows a strict allowlist approach, preserving only elements and attributes required for static vector graphics.

The objective is to provide a simple, reusable, and open-source security component that reduces the attack surface when processing SVG files, without requiring applications to implement their own SVG sanitization logic.

Installation

python -m pip install -e './svg-sanitize[test]'
# or, when inside the extracted directory:
python -m pip install -e '.[test]'

Python 3.10+; the only runtime dependency is defusedxml.

Python usage

from svg_sanitize import sanitize_svg, SVGValidationError

untrusted_svg = b'''<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)">
  <script>alert(1)</script>
  <rect width="80" height="40" fill="red"/>
</svg>'''

try:
    passive_svg = sanitize_svg(untrusted_svg)
except SVGValidationError as exc:
    print(f"SVG rejected: {exc}")
else:
    print(passive_svg)
    # <svg xmlns="http://www.w3.org/2000/svg"><rect width="80" height="40" fill="red" /></svg>

For custom input and rendering limits:

from svg_sanitize import Limits, sanitize_svg

output = sanitize_svg(untrusted_svg, limits=Limits(max_input_bytes=100_000, max_nodes=1_000))

CLI

svg-sanitize examples/malicious.svg -o clean.svg
# alternatively:
python -m svg_sanitize examples/malicious.svg -o clean.svg

Run tests:

python -m pytest -q

CI and PyPI releases

The GitHub Actions workflow runs the tests on Python 3.10–3.14 for pull requests, pushes to main, and published GitHub releases. After the tests pass, it builds and checks both the source distribution and wheel. Only a published release triggers an upload to PyPI, using those checked artifacts.

Before the first release:

  1. Create a GitHub Actions environment named pypi in this repository. Add any desired deployment protection rules there.
  2. Configure a PyPI trusted publisher for the svg-sanitize project with owner adulau, repository svg-sanitize, workflow filename ci.yml, and environment name pypi. If the project does not exist yet, configure a pending publisher in your PyPI account instead.

No PyPI API token or GitHub repository secret is required. For each release, update project.version in pyproject.toml, merge the change, then create and publish a GitHub release from that commit with a matching tag, such as v0.1.0 or 0.1.0. A mismatched tag fails before publishing. Each upload must use a new version because PyPI does not allow replacing previously uploaded files. Published prereleases also run this workflow; drafts do not.

What is allowed

Only svg, g, rect, circle, ellipse, line, polyline, polygon, path, text, and tspan. Numeric dimensions and coordinates, path data, point lists, limited transforms, limited named/hex/rgb colors, static stroke/ fill and selected text formatting properties are validated individually.

SVGs using gradients, filters, clipping paths, <use>, images, embedded fonts, CSS, masks, symbols, hyperlinks, animation or scripting will lose those features. In particular, gradient fills fill="url(#id)" are discarded, including local URLs. This is an explicit security/functionality tradeoff.

What is forbidden

  • JavaScript and scriptable elements; onload, onclick, and all event handlers.
  • CSS (<style>, style=), including @import, url(...), and expressions.
  • All links and resources, including href, xlink:href, external and internal references, data: URLs, <image>, <use>, fonts, filters and gradients.
  • <foreignObject> and arbitrary/HTML/XML namespace elements.
  • SVG animation and SMIL (animate, set, animateTransform, etc.).
  • DTDs, XML entity declarations, external entity references, malformed XML.
  • Unknown attributes (including id, class, xml:base, and xmlns:xlink).

Unknown elements are removed with their complete subtrees; the input is never copied wholesale to output. The implementation also enforces limits on input bytes, output bytes, node count, depth, attribute sizes and text sizes.

Deployment advice

For untrusted-user uploads, make the sanitized document only one defense layer:

  1. Keep the original SVG out of any publicly served directory.
  2. Serve the sanitized output with Content-Type: image/svg+xml, ideally from a separate cookieless domain with Content-Disposition: attachment for downloads. For display, prefer <img src="..."> rather than inline SVG.
  3. Use CSP restricting script-src 'none'; object-src 'none'; base-uri 'none'; default-src 'none'; img-src 'self' data: on the serving endpoint where appropriate, and ensure application CSP is also restrictive.
  4. Treat the output as untrusted in any subsequent XML/SVG manipulation pipeline; downstream software might add back risky features. If preserving more complex SVG features is necessary, consider rasterizing with a hardened, resource- isolated renderer instead of broadening the allowlist.
  5. Use browser-based regression tests, fuzz tests, and multiple-engine testing before relying on the package for production security.

No SVG sanitizer can promise protection against all future rendering-engine vulnerabilities. This prototype targets active-content injection and external resource-loading pathways, not malicious graphical impersonation or renderer memory-corruption bugs.

MIT licensed (see LICENSE).

Metadata

Release files for svg-sanitize 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for svg-sanitize 0.1.0
File Size Uploaded Provenance
svg_sanitize-0.1.0.tar.gz 16.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for svg-sanitize 0.1.0
File Interpreter ABI Platform
svg_sanitize-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 28.4 kB

Release files / svg_sanitize-0.1.0.tar.gz

Download URL svg_sanitize-0.1.0.tar.gz
Size 16.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3e5f0e2118e6d57e15ba41a6184acb17746997976d06a415dce2d6d1d4dbe8e4
BLAKE2b-256 checksum
How to use checksums
257f8c139338220cd9b0372b38c3ea70fcac550f215816bc81dbe54927d10a14
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / svg_sanitize-0.1.0-py3-none-any.whl

Download URL svg_sanitize-0.1.0-py3-none-any.whl
Size 11.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
75aaf8f89d57fcb9ad6129f69624d0142c94b8b3180ff9a356087778648ce2c0
BLAKE2b-256 checksum
How to use checksums
0d4036666926c0e81ce3a0b057cb5f9b09a49d5ec0ef5c3fadee9eb7dd66ae09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for svg-sanitize 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for svg_sanitize-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page