diff --git a/Plugin/ImageContentPolyglotValidator.php b/Plugin/ImageContentPolyglotValidator.php
new file mode 100644
index 0000000..7f417fe
--- /dev/null
+++ b/Plugin/ImageContentPolyglotValidator.php
@@ -0,0 +1,58 @@
+getBase64EncodedData();
+ if ($base64 === '') {
+ return $result;
+ }
+
+ $decoded = base64_decode($base64, true);
+ if ($decoded === false) {
+ throw new InputException(new Phrase('Invalid image payload encoding.'));
+ }
+
+ $haystack = strtolower($decoded);
+ foreach (self::DANGEROUS_MARKERS as $marker) {
+ if (strpos($haystack, strtolower($marker)) !== false) {
+ throw new InputException(
+ new Phrase('The uploaded image content is not allowed.')
+ );
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/Test/Unit/Plugin/ImageContentPolyglotValidatorTest.php b/Test/Unit/Plugin/ImageContentPolyglotValidatorTest.php
new file mode 100644
index 0000000..3bbfa6b
--- /dev/null
+++ b/Test/Unit/Plugin/ImageContentPolyglotValidatorTest.php
@@ -0,0 +1,88 @@
+model = new ImageContentPolyglotValidator();
+ $this->subjectMock = $this->createMock(ImageContentValidator::class);
+ $this->imageContentMock = $this->createMock(ImageContentInterface::class);
+ }
+
+ public function testValidGifPasses(): void
+ {
+ $validGif = base64_encode('GIF89a' . str_repeat("\x00", 10));
+ $this->imageContentMock->method('getBase64EncodedData')->willReturn($validGif);
+
+ $result = $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock);
+ $this->assertTrue($result);
+ }
+
+ public function testPhpContentMarkerIsRejected(): void
+ {
+ $payload = 'imageContentMock->method('getBase64EncodedData')->willReturn(base64_encode($payload));
+
+ $this->expectException(InputException::class);
+ $this->expectExceptionMessage('The uploaded image content is not allowed.');
+
+ $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock);
+ }
+
+ public function testGifPhpPolyglotIsRejected(): void
+ {
+ $payload = 'GIF89a;';
+ $this->imageContentMock->method('getBase64EncodedData')->willReturn(base64_encode($payload));
+
+ $this->expectException(InputException::class);
+ $this->expectExceptionMessage('The uploaded image content is not allowed.');
+
+ $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock);
+ }
+
+ public function testEmptyDataPasses(): void
+ {
+ $this->imageContentMock->method('getBase64EncodedData')->willReturn('');
+
+ $result = $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock);
+ $this->assertTrue($result);
+ }
+
+ public function testFalseCoreValidationResultIsPreserved(): void
+ {
+ $this->imageContentMock->expects($this->never())->method('getBase64EncodedData');
+
+ $result = $this->model->afterIsValid($this->subjectMock, false, $this->imageContentMock);
+ $this->assertFalse($result);
+ }
+
+ public function testInvalidEncodingThrowsException(): void
+ {
+ // "!!!" is not valid base64 with strict check
+ $this->imageContentMock->method('getBase64EncodedData')->willReturn('!!!');
+
+ $this->expectException(InputException::class);
+ $this->expectExceptionMessage('Invalid image payload encoding.');
+
+ $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock);
+ }
+}
diff --git a/Test/bootstrap.php b/Test/bootstrap.php
new file mode 100644
index 0000000..7f630cb
--- /dev/null
+++ b/Test/bootstrap.php
@@ -0,0 +1,36 @@
+
-
+
+
-
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..1ad5079
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,12 @@
+
+
+
+
+ Test/Unit
+
+
+