diff --git a/composer.json b/composer.json
index 435cabe30..122cdebd7 100644
--- a/composer.json
+++ b/composer.json
@@ -14,6 +14,7 @@
"require": {
"php": "8.2.*|8.3.*|8.4.*",
"code16/laravel-content-renderer": "^1.1.0",
+ "enshrined/svg-sanitize": "^0.21",
"intervention/image": "^3.4",
"intervention/image-laravel": "^1.2",
"laravel/framework": "^10.0|^11.0",
diff --git a/src/Form/Fields/Formatters/UploadFormatter.php b/src/Form/Fields/Formatters/UploadFormatter.php
index 79ee20cac..12fa4b4a6 100644
--- a/src/Form/Fields/Formatters/UploadFormatter.php
+++ b/src/Form/Fields/Formatters/UploadFormatter.php
@@ -8,6 +8,7 @@
use Code16\Sharp\Form\Fields\SharpFormUploadField;
use Code16\Sharp\Form\Fields\Utils\SharpFormFieldWithUpload;
use Code16\Sharp\Utils\FileUtil;
+use enshrined\svgSanitize\Sanitizer;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Support\Arr;
use Intervention\Image\Image;
@@ -80,6 +81,11 @@ public function fromFront(SharpFormField $field, string $attribute, $value): ?ar
$storage->put($storedFilePath, $fileContent);
+ // Sanitize SVG files to prevent XSS attacks
+ if ($field->isShouldSanitizeSvg() && $storage->mimeType($storedFilePath) === 'image/svg+xml') {
+ $this->sanitizeSvg($storage, $storedFilePath);
+ }
+
return [
'file_name' => $storedFilePath,
'size' => $storage->size($storedFilePath),
@@ -184,4 +190,17 @@ protected function returnAfterNoChangeWasMade(?array $data): ?array
? $data
: ($data === null ? null : []);
}
+
+ protected function sanitizeSvg($storage, string $filePath): void
+ {
+ $sanitizer = new Sanitizer();
+ $sanitizer->minify(true);
+ $sanitizer->removeXMLTag(true);
+
+ $sanitizedSvg = $sanitizer->sanitize($storage->get($filePath));
+
+ if ($sanitizedSvg !== false) {
+ $storage->put($filePath, $sanitizedSvg);
+ }
+ }
}
diff --git a/src/Form/Fields/Utils/SharpFormFieldWithUpload.php b/src/Form/Fields/Utils/SharpFormFieldWithUpload.php
index c6be5e2f4..e06433dce 100644
--- a/src/Form/Fields/Utils/SharpFormFieldWithUpload.php
+++ b/src/Form/Fields/Utils/SharpFormFieldWithUpload.php
@@ -15,6 +15,7 @@ trait SharpFormFieldWithUpload
protected ?bool $transformKeepOriginal = null;
protected bool $compactThumbnail = false;
protected bool $shouldOptimizeImage = false;
+ protected bool $shouldSanitizeSvg = true;
protected string|array|null $fileFilter = null;
public function setMaxFileSize(float $maxFileSizeInMB): self
@@ -52,6 +53,18 @@ public function isShouldOptimizeImage(): bool
return $this->shouldOptimizeImage;
}
+ public function setSanitizeSvg(bool $shouldSanitizeSvg = true): self
+ {
+ $this->shouldSanitizeSvg = $shouldSanitizeSvg;
+
+ return $this;
+ }
+
+ public function isShouldSanitizeSvg(): bool
+ {
+ return $this->shouldSanitizeSvg;
+ }
+
public function setCompactThumbnail(bool $compactThumbnail = true): self
{
$this->compactThumbnail = $compactThumbnail;
diff --git a/tests/Unit/Form/Fields/Formatters/UploadFormatterTest.php b/tests/Unit/Form/Fields/Formatters/UploadFormatterTest.php
index d5fa4c3bb..ad7727b67 100644
--- a/tests/Unit/Form/Fields/Formatters/UploadFormatterTest.php
+++ b/tests/Unit/Form/Fields/Formatters/UploadFormatterTest.php
@@ -355,4 +355,56 @@ public function we_return_full_object_after_only_transformations_if_configured()
),
);
}
+
+ /** @test */
+ public function we_sanitize_svg_files_by_default()
+ {
+ $maliciousSvg = '';
+
+ Storage::disk('local')->put('/tmp/malicious.svg', $maliciousSvg);
+
+ $field = SharpFormUploadField::make('upload')
+ ->setStorageBasePath('data')
+ ->setStorageDisk('local');
+
+ (new UploadFormatter())
+ ->fromFront(
+ $field,
+ 'attribute',
+ [
+ 'name' => 'malicious.svg',
+ 'uploaded' => true,
+ ],
+ );
+
+ $sanitizedContent = Storage::disk('local')->get('data/malicious.svg');
+ $this->assertStringNotContainsString('';
+
+ Storage::disk('local')->put('/tmp/test.svg', $svgWithScript);
+
+ $field = SharpFormUploadField::make('upload')
+ ->setStorageBasePath('data')
+ ->setStorageDisk('local')
+ ->setSanitizeSvg(false);
+
+ (new UploadFormatter())
+ ->fromFront(
+ $field,
+ 'attribute',
+ [
+ 'name' => 'test.svg',
+ 'uploaded' => true,
+ ],
+ );
+
+ $content = Storage::disk('local')->get('data/test.svg');
+ $this->assertStringContainsString('