Wednesday, October 28, 2026

safe-svg for catching XSS payloads hidden inside uploaded SVG files

safe-svg for catching XSS payloads hidden inside uploaded SVG files

safe-svg is a lightweight Java library that inspects uploaded SVG content for dangerous elements and attributes, helping developers prevent stored XSS in applications that accept image uploads.

Toolbgalek/safe-svg — Java library that validates uploaded SVG files for malicious content such as embedded script elements
CategoryDefensive input validation / content sanitization library (Java, Apache-2.0)
Primary UseScreening user-uploaded SVG images before serving them, so that files containing script, event handlers, or other unsafe elements are rejected and stored XSS is prevented
Safe UsePurely defensive library intended for developers building authorized applications; it validates files on systems you own and leaves no footprint on third parties
Telemetry NoteLibrary runs in-process; its use is observable only through application logs — rejected uploads surface as ValidationResult violations with getOffendingElements() output

safe-svg from bgalek addresses a class of bug that keeps biting teams that let users upload images: the SVG format is not just an image, it is XML that browsers will happily execute script from. The repository's README opens with a direct question — are you aware that SVG can cause XSS? — and links to a Sekurak writeup (in Polish) documenting exactly how an upload endpoint that accepts SVG files becomes a stored cross-site scripting vector the moment that file is later browsable by other users. The library's stated purpose is simple and lightweight detection of malicious content inside uploaded SVG files, which positions it firmly as a defensive, developer-facing control rather than an offensive scanner.

The core workflow shown in the README is deliberately boring, and that is the point. You construct a validator via the builder pattern — SvgSecurityValidator.builder().build() — then hand it a String or byte[] containing the raw SVG document, with byte[] input decoded as UTF-8. The validate() call returns a ValidationResult, and your application checks validation.hasViolations() before deciding whether to accept the upload. When something suspicious is found, validation.getOffendingElements() tells you which elements tripped the detector, which is exactly the detail you want in an audit log or a user-facing rejection message.

Internally, the tool works as an allowlist-style structural analysis of the XML tree. Rather than trying to pattern-match obfuscated payloads, it parses the document and compares the elements and attributes present against a set of known-safe ones. The README exposes this model explicitly through the customization API: withAdditionalElements(elements) and withAdditionalAttributes(attributes) let you extend the allowlist if your application genuinely needs elements the default policy considers non-safe. That inversion matters operationally — you are explicitly opting in to risk rather than hoping a blocklist caught the latest bypass, and the default posture is the conservative one.

The most security-relevant architectural detail in the README is the optional strict syntax validation, enabled with withSyntaxValidation(). When active, malformed XML causes an InvalidXMLSyntaxException rather than slipping through. Critically, the documentation states that parsing uses an XXE-hardened parser: external entities and DTDs are never resolved, and entity expansion is disabled. This is the right call, because an SVG validator that itself parsed hostile XML naively would trade one vulnerability class (stored XSS) for another (XXE and billion-laughs style entity expansion). The fact that this hardening is documented in the README suggests the author has thought about the validator as a trust boundary, not just a string filter.

The demonstration payload in the README is instructional rather than weaponized: an SVG containing a polygon plus a script element firing a harmless alert('Hello, world!'). Its purpose is as a self-test — upload it to your own application, and if it renders with the script executing, you have confirmed you are vulnerable to XSS via SVG uploads. This is a classic defensive triage technique for your own systems, and it doubles as the natural acceptance test for the validator integration itself.

Integration is a single dependency line, which reflects the lightweight claim. The README shows a Groovy coordinate for Gradle: implementation "com.github.bgalek.security.svg:safe-svg:1.2.0", and the badge row confirms the artifact is published to Maven Central under the com.github.bgalek.security.svg group. For a JVM web stack — Spring Boot, Jakarta EE, or anything else accepting multipart uploads — that means the barrier to adoption is essentially one build line plus a call in your upload handler. Because validation happens in-process, there is no sidecar service to operate and no additional network trust boundary to review.

The project's engineering signals are decent for a small library. Repository metadata shows it written in Java, licensed Apache-2.0, tagged with security, svg, xss, and xss-detection topics, plus hacktoberfest. The README badge row indicates continuous integration via GitHub Actions on the main branch, Codecov coverage tracking, a SonarCloud quality gate, and a Libraries.io dependency status badge. None of these guarantee absence of bugs, but for a security control the combination of coverage tracking and static analysis gating is a meaningful maturity indicator, and the Maven Central release badge suggests regular versioned releases.

Where does this fit in an authorized workflow? The obvious spot is immediately after multipart parsing and before persistence: any endpoint accepting user-supplied SVG for avatars, logos, diagrams, or attachments should run the bytes through validate() and reject on hasViolations(). A second, often-missed integration point is on the read path of legacy content — running existing stored SVG assets through the validator during a security review can surface files uploaded before the control existed. Red teams doing authorized assessments can also use the same check to verify a client's upload pipeline actually filters, though the tool itself is built for the defenders.

The customization API deserves a caution note. withAdditionalElements() exists precisely because some applications legitimately use richer SVG features, but every element you add to the allowlist widens the attack surface you are responsible for reasoning about. Treat any pull request that adds entries there with the same scrutiny as one that disables a CSP directive — it should carry a justification and ideally a reference to why the element cannot carry executable content. The default allowlist, being maintained by a security-motivated author, is the safer default for the majority of upload pipelines.

The ValidationResult design also makes the tool easy to wire into observability. Because offending elements are enumerated rather than collapsed into a boolean, you can log which elements appear in rejected uploads, giving defenders a live signal of upload-probing behavior against their application. Aggregated over time, that telemetry distinguishes benign users uploading unusual-but-safe graphics from someone systematically probing whether your filter understands foreignObject, event handler attributes, or use-based references.

It is worth being clear about scope: safe-svg validates file content, not serving context. Even with the validator in place, defense in depth argues for serving user-uploaded content from a separate origin, setting a restrictive Content-Security-Policy, and using Content-Type headers that prevent an SVG from being interpreted as HTML. The library is one layer in that stack — arguably the most fundamental one, since it keeps hostile documents out of storage entirely — but teams that deploy it should not treat it as a substitute for the surrounding hardening.

For a focused, single-purpose library, bgalek/safe-svg does its one job with the right instincts: allowlist validation, hardened XML parsing, enumerable violations, and trivial JVM integration via Maven Central. If your application accepts SVG uploads today and has no content validation, the README's own demonstration suggests the fastest path to knowing whether you have a problem — and this library is the correspondingly fast fix.

Official project repository for bgalek/safe-svg.
Download Tool

Educational analysis for authorized security professionals. Use only in controlled, authorized environments.

Share articleFacebookXLinkedIn

Continue exploring

Browse all articles →

0 comentários:

Post a Comment

Note: Only a member of this blog may post a comment.