svg-hush
A Rust library and CLI that strips scripting, cross-origin links, and other XSS vectors from untrusted SVG files.
Repository Health
Technical Analysis
svg-hush is a Rust crate built by Cloudflare that turns arbitrary, untrusted SVG files into safe images you can serve or embed without risking cross-site scripting, SEO spam, or cross-origin tracking. SVGs aren’t just images — they’re full XML documents that can carry script tags, event handler attributes, external stylesheets, and links to other domains. svg-hush parses each file with a strict streaming XML reader, drops any element or attribute that isn’t on an explicit allowlist, rewrites relative and cross-origin URLs to strip their host, and neutralizes url() references inside inline styles and style blocks.
The library exposes a single Filter type with a filter() method that reads from any io::Read and writes to any io::Write, plus an optional callback for deciding what to do with data: URLs (keep, drop, or re-encode). A companion CLI binary wraps the same filtering logic for one-off sanitization from the command line or a build pipeline. The project ships with unit and integration tests, a fuzzing harness under fuzz/, and a documented regression test for a real disclosed vulnerability (a HackerOne-reported namespace-URI attribute-injection bug), reflecting its use as a defense-in-depth layer for services that must accept and re-serve user-uploaded SVGs.
What You Get
- A Filter struct with a streaming filter() API that reads any io::Read source and writes a sanitized SVG to any io::Write destination
- An allowlist of SVG elements and attributes covering shapes, gradients, filters, and text, with everything else silently dropped
- URL rewriting that strips scheme and host from every href/xlink:href, url() and CSS reference, converting them to same-origin relative paths
- A pluggable data: URL filter callback (with a ready-made allow_standard_images preset) to control which embedded image types survive
- A standalone svg_hush CLI binary for sanitizing files from stdin/stdout or file-to-file without writing any Rust code
Common Use Cases
- Sanitizing user-uploaded SVG avatars, logos, or attachments before storing or serving them from a CDN or object storage bucket
- Adding a defense-in-depth filtering pass in front of an SVG upload endpoint, independent of a Content-Security-Policy header
- Stripping tracking pixels and cross-origin resource references from SVGs pulled in from third-party or user-supplied content
- Pre-processing SVG assets in a build pipeline before bundling them into a web app or design system
Under The Hood
Architecture
svg-hush is a single-pass, event-driven filter built on the xml crate’s streaming reader and writer: each XmlEvent from the parser (REvent) is inspected and translated into an output event (WEvent) with no intermediate DOM. Element admission is decided in is_allowed_element against a static allowlist (ALLOWED_SVG_ELEMENTS), and attribute admission in filter_attribute against a typed table (attrs::ATTRS) that classifies each attribute as AnyAscii, Keyword, Url, UrlFunc, StyleSheet, etc. A small state machine tracks skipping depth to drop disallowed subtrees and accumulating_css_text to buffer and filter <style> element contents before re-emitting them. Because the entire security guarantee rests on is_allowed_element and filter_attribute, the library keeps that logic centralized in lib.rs rather than spreading it across the small attrs.rs and data_url_filter.rs submodules, so the one place that must stay correct is easy to audit.
Tech Stack
The crate targets Rust edition 2024 and depends on xml 1.4 for streaming XML parse/emit, url 2.5.8 for URL parsing and relative-path resolution, data-url 0.3.2 and base64 for decoding and re-encoding data: URLs, and quick-error 2.0.1 to define the typed FError enum. There’s no async runtime, web framework, or database — it’s a pure library crate plus a thin main.rs binary that only uses std::fs/std::io. Release metadata configures cargo-release tagging and docs.rs to build for x86_64-unknown-linux-gnu with linked source definitions; a separate fuzz/ crate wires up cargo-fuzz targets and a seed corpus for continuous fuzzing of the filter.
Code Quality
Tests are colocated as #[test] functions in lib.rs for the CSS/url() tokenizer and URL-rewriting logic, plus a dedicated tests/tests.rs integration test that round-trips a fixture file and re-filters already-filtered output to confirm idempotency. One test is a named regression for a disclosed HackerOne vulnerability, with inline comments explaining the exact attack and the upstream fix it guards against — an unusually rigorous practice for a small crate. Errors are represented with a typed FError enum (via quick_error!) rather than swallowed or stringly-typed, and propagated with ?. The only CI workflow present runs a Semgrep security scan on every push and PR; there’s no visible workflow that runs cargo test or cargo fuzz, so test execution currently appears to rely on local/manual runs and pre-release checks rather than continuous integration.
What Makes It Unique
Instead of depending on a general-purpose HTML/CSS parser or building a DOM-based sanitizer the way DOMPurify does, svg-hush implements a deliberately narrow, hand-rolled tokenizer for url() references inside CSS and inline styles, with extensive inline comments justifying the tradeoff: matching a full browser-grade parser exactly is hard, so the library instead throws away anything that looks ambiguous or risky to parse. It also rewrites every URL to a same-origin relative path rather than merely blocklisting dangerous schemes, which closes off both scripting and cross-origin tracking/SEO-spam vectors in a single pass — a more aggressive and specific stance than most sanitizers take on image assets.