From d202c9c32f50954326be9d505c65ed745d43357c Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Date: Mon, 8 Jun 2026 11:31:41 +0100
Subject: [PATCH] Minor: sanitize SVG (#6572)
# Description of Changes
Sanitize SVG
---
## Checklist
### General
- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings
### Documentation
- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
### Translations (if applicable)
- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)
### UI Changes (if applicable)
- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
### Testing (if applicable)
- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
---
.../api/misc/OverlayImageController.java | 5 ++
.../software/SPDF/utils/SvgOverlayUtil.java | 12 ++++-
.../api/misc/OverlayImageControllerTest.java | 48 +++++++++++++++++++
3 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java
index c55597b47..557c77c76 100644
--- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java
+++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java
@@ -25,6 +25,7 @@ import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.enumeration.ResourceWeight;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
+import stirling.software.common.util.SvgSanitizer;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@@ -36,6 +37,7 @@ public class OverlayImageController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final TempFileManager tempFileManager;
+ private final SvgSanitizer svgSanitizer;
@AutoJobPostMapping(
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
@@ -61,6 +63,9 @@ public class OverlayImageController {
byte[] imageBytes = imageFile.getBytes();
boolean isSvg = SvgOverlayUtil.isSvgImage(imageBytes);
+ if (isSvg) {
+ imageBytes = svgSanitizer.sanitize(imageBytes);
+ }
try (PDDocument document = pdfDocumentFactory.load(pdfBytes)) {
int pages = document.getNumberOfPages();
diff --git a/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java b/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java
index 253d9c687..b58bb055f 100644
--- a/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java
+++ b/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java
@@ -10,6 +10,7 @@ import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.gvt.GraphicsNode;
+import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
@@ -39,7 +40,16 @@ public class SvgOverlayUtil {
svgDoc = factory.createSVGDocument("file:///overlay.svg", inputStream);
}
- UserAgent userAgent = new UserAgentAdapter();
+ UserAgent userAgent =
+ new UserAgentAdapter() {
+ @Override
+ public void checkLoadExternalResource(
+ ParsedURL resourceURL, ParsedURL docURL) {
+ throw new SecurityException(
+ "External resource loading is disabled for SVG overlays: "
+ + resourceURL);
+ }
+ };
DocumentLoader loader = new DocumentLoader(userAgent);
BridgeContext ctx = new BridgeContext(userAgent, loader);
ctx.setDynamicState(BridgeContext.DYNAMIC);
diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java
index 308518e8f..6a13fb304 100644
--- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java
+++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java
@@ -32,6 +32,7 @@ import org.springframework.mock.web.MockMultipartFile;
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
+import stirling.software.common.util.SvgSanitizer;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@@ -52,6 +53,7 @@ class OverlayImageControllerTest {
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
@Mock private TempFileManager tempFileManager;
+ @Mock private SvgSanitizer svgSanitizer;
@InjectMocks private OverlayImageController controller;
@@ -205,6 +207,52 @@ class OverlayImageControllerTest {
mockDoc.close();
}
+ @Test
+ void overlayImage_svgInput_sanitizedBeforeOverlay() throws Exception {
+ byte[] maliciousSvg =
+ ("")
+ .getBytes();
+ byte[] sanitized =
+ ("")
+ .getBytes();
+ when(svgSanitizer.sanitize(maliciousSvg)).thenReturn(sanitized);
+
+ MockMultipartFile svgFile =
+ new MockMultipartFile("imageFile", "overlay.svg", "image/svg+xml", maliciousSvg);
+ OverlayImageRequest request = new OverlayImageRequest();
+ request.setFileInput(pdfFile);
+ request.setImageFile(svgFile);
+ request.setX(0);
+ request.setY(0);
+ request.setEveryPage(false);
+
+ PDDocument mockDoc = new PDDocument();
+ mockDoc.addPage(new PDPage(PDRectangle.A4));
+ when(pdfDocumentFactory.load(any(byte[].class))).thenReturn(mockDoc);
+
+ try (MockedStatic mockedWebResponse =
+ mockStatic(WebResponseUtils.class)) {
+ mockedWebResponse
+ .when(
+ () ->
+ WebResponseUtils.pdfFileToWebResponse(
+ any(TempFile.class), anyString()))
+ .thenReturn(streamingOk("result".getBytes()));
+
+ controller.overlayImage(request);
+ }
+ mockDoc.close();
+
+ verify(svgSanitizer).sanitize(maliciousSvg);
+ }
+
@Test
void overlayImage_withCoordinates_usesXY() throws Exception {
OverlayImageRequest request = new OverlayImageRequest();