Skip to main content

README.md

# SvgSanitizer

Vetted SVG sanitizer for Elixir. Thin precompiled NIF wrapping Cloudflare's
[`svg-hush`](https://crates.io/crates/svg-hush) Rust crate, so applications
can accept user-uploaded SVG without authoring their own allowlist scrubber
`<script>`, event handlers, foreign objects, external references, and
javascript:/data: URL vectors are stripped, raster image data URLs (PNG /
JPEG / GIF / WebP) are preserved so sanitized SVGs stay self-contained.

The NIF runs on a dirty CPU scheduler and converts Rust panics into
`{:error, term}` rather than crashing the BEAM node.

## Installation

```elixir
def deps do
  [
    {:svg_sanitizer, "~> 0.1"}
  ]
end
```

Precompiled artifacts are published to GitHub releases for these targets;
`mix deps.get` downloads the right one — no Rust toolchain required:

- `aarch64-unknown-linux-gnu`
- `x86_64-unknown-linux-gnu`

**OTP 26+ required** (NIF 2.17). Earlier NIF versions will be added on demand.

**macOS users:** v0.1 doesn't ship precompiled macOS artifacts (the
`rustler-precompiled-action` mishandles `cross` on Apple Silicon; tracked
for v0.2). Build from source by setting `SVG_SANITIZER_BUILD=1`; you'll need
`cargo` installed.

## Usage

```elixir
case SvgSanitizer.sanitize(user_uploaded_svg) do
  {:ok, clean} ->
    store_asset(clean)

  {:error, reason} ->
    # reason is one of:
    #   :invalid_input    — input wasn't a binary
    #   :input_too_large  — over 5 MB; rejected without parsing
    #   :parse_error      — svg-hush rejected as malformed
    #   :panic            — Rust layer panicked (caught, BEAM safe)
    #   :alloc_failed     — out of memory while building output
    Logger.warning("svg sanitize failed: #{reason}")
    reject_upload(reason)
end
```

Always handle `{:error, _}`. SVG input from users *will* hit one of those
branches eventually; pattern-matching only on `{:ok, _}` is a `MatchError`
waiting to happen.

## Why a separate package

Elixir has no purpose-built SVG sanitizer; `html_sanitize_ex` is HTML-only
and pointing it at SVG amounts to hand-rolling an allowlist (which is what
makes SVG sanitization dangerous — the [TYPO3 PSA-2025-001
advisory](https://typo3.org/security/advisory/typo3-psa-2025-001) traces a
real-world bypass to exactly that). `svg-hush` is Cloudflare-maintained,
allowlist-based, and security-focused; this package is the thin glue that
makes it available to Phoenix / Plug / LiveView applications.

## License

Apache-2.0 — matches the upstream `svg-hush` crate.