or onload="" * payloads. It isn't. The upload-time sanitizer strips both shapes * before the file reaches disk, but the report was accurate about * the inline-serving path, so this test locks in the upload-time * sanitization so a future refactor can't quietly remove the SVG * branch and reintroduce the exploit chain. */ class SvgUploadSanitizationTest extends TestCase { private array $tempFiles = []; protected function setUp(): void { parent::setUp(); Storage::fake(); } protected function tearDown(): void { foreach ($this->tempFiles as $path) { @unlink($path); } parent::tearDown(); } private function realUpload(string $clientName, string $content): UploadedFile { // A real temp file is required for finfo to sniff the MIME // correctly. UploadedFile::fake() derives its mime from the // filename, which would trivially "pass" the SVG detection // without exercising the real sanitize path. $path = tempnam(sys_get_temp_dir(), 'snipeit_svg_'); file_put_contents($path, $content); $this->tempFiles[] = $path; return new UploadedFile($path, $clientName, null, null, true); } public function test_svg_upload_strips_inline_script_element(): void { $malicious = <<<'SVG' SVG; $upload = $this->realUpload('malicious.svg', $malicious); $storedName = (new UploadFileRequest)->handleFile('private_uploads/assets/', 'asset-1', $upload); $storedContents = Storage::get('private_uploads/assets/'.$storedName); $this->assertStringNotContainsString(' elements from uploaded SVGs.'); $this->assertStringNotContainsString('alert(', $storedContents, 'Sanitizer must strip script bodies from uploaded SVGs.'); // Legitimate SVG content should survive so the file is still // a usable image after sanitization. $this->assertStringContainsString(' SVG; $upload = $this->realUpload('onload.svg', $malicious); $storedName = (new UploadFileRequest)->handleFile('private_uploads/assets/', 'asset-1', $upload); $storedContents = Storage::get('private_uploads/assets/'.$storedName); $this->assertStringNotContainsString('onload', $storedContents, 'Sanitizer must strip on* event handlers from uploaded SVGs.'); $this->assertStringNotContainsString('alert(', $storedContents); $this->assertStringContainsString(' SVG; $upload = $this->realUpload('logo.svg', $clean); $storedName = (new UploadFileRequest)->handleFile('private_uploads/assets/', 'asset-1', $upload); $storedContents = Storage::get('private_uploads/assets/'.$storedName); $this->assertStringContainsString('assertStringContainsString('fill="green"', $storedContents); } }