diff --git a/projects/openexr/Dockerfile b/projects/openexr/Dockerfile
index 2cc49455f768..b123f0cba396 100644
--- a/projects/openexr/Dockerfile
+++ b/projects/openexr/Dockerfile
@@ -16,6 +16,17 @@
 
 FROM gcr.io/oss-fuzz-base/base-builder
 RUN apt-get update && apt-get install -y make autoconf automake libtool zlib1g-dev
+RUN apt-get update -y && \
+    apt-get install -y make autoconf automake libtool zlib1g-dev \
+    binutils ninja-build liblzma-dev libz-dev pkg-config
+
 RUN git clone --depth 1 https://github.com/AcademySoftwareFoundation/openexr openexr
+
+RUN git clone --depth 1 https://github.com/google/libprotobuf-mutator.git
+RUN (mkdir LPM && cd LPM && cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release && ninja)
+
 WORKDIR openexr
+
+COPY *.dict *.proto *.h *.cc $SRC/
+
 COPY build.sh $SRC/
diff --git a/projects/openexr/build.sh b/projects/openexr/build.sh
index 2b4f07f92ee9..b501dda98eb1 100755
--- a/projects/openexr/build.sh
+++ b/projects/openexr/build.sh
@@ -32,3 +32,26 @@ cmake -S $SRC/openexr -B $BUILD_DIR --preset oss_fuzz
 cmake --build $BUILD_DIR --target oss_fuzz -j"$(nproc)"
 cmake --install $BUILD_DIR --component oss_fuzz
 
+# ProtobufMutator does not currently support MSan
+if [[ $CFLAGS = *sanitize=memory* ]]; then
+    exit 0
+fi
+
+# For fuzz-introspector, exclude files we don't want
+export FUZZ_INTROSPECTOR_CONFIG=$SRC/fuzz_introspector_exclusion.config
+cat > $FUZZ_INTROSPECTOR_CONFIG <<EOF
+FILES_TO_AVOID
+LPM
+genfiles
+EOF
+
+# Compile json proto.
+rm -rf genfiles && mkdir genfiles && $SRC/LPM/external.protobuf/bin/protoc exr.proto --cpp_out=genfiles --proto_path=$SRC
+
+# Compile LPM fuzzer.
+$CXX $CXXFLAGS -std=c++14 -pthread ${INCLUDES[@]} -I genfiles -I $SRC/libprotobuf-mutator/ -I $SRC/LPM/external.protobuf/include $LIB_FUZZING_ENGINE \
+  $SRC/exr_proto_fuzzer.cc genfiles/exr.pb.cc $SRC/exr_proto_converter.cc \
+  $SRC/LPM/src/libfuzzer/libprotobuf-mutator-libfuzzer.a \
+  $SRC/LPM/src/libprotobuf-mutator.a \
+  -Wl,--start-group $SRC/LPM/external.protobuf/lib/lib*.a -Wl,--end-group \
+  ${LIBS[@]} -lz -o $OUT/exr_proto_fuzzer
diff --git a/projects/openexr/exr.proto b/projects/openexr/exr.proto
new file mode 100644
index 000000000000..a71f1c5ce5e1
--- /dev/null
+++ b/projects/openexr/exr.proto
@@ -0,0 +1,73 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+syntax = "proto2";
+// Very simple proto description of the EXR format,
+// described at https://openexr.com/en/latest/OpenEXRFileLayout.html
+
+// we will calculate xMax and yMax from with width & height
+message Exr_box2i {
+  required int32 xMin = 1;
+  required int32 yMin = 2;
+  required int32 w = 3;
+  required int32 h = 4;
+}
+
+message Exr_channel {
+    required string chname = 1;
+    required int32 pixel_type = 2;
+    required uint32 pLinear = 3;
+    // here there should be three \x00 chars
+    required int32 xSampling = 4;
+    required int32 ySampling = 5;
+}
+
+message Exr_chlist {
+  // this should be null terminated
+  repeated Exr_channel channels = 1;
+}
+
+message Exr_compression {
+  required uint32 compression = 1;
+}
+
+message Exr_v2f {
+  required float f1 = 1;
+  required float f2 = 2;
+}
+
+message Exr_lineOrder {
+  required uint32 lineOrder = 1;
+}
+
+message ExrHeader {
+  required Exr_chlist channel_list = 1;
+  required Exr_compression compression = 2;
+  required Exr_box2i dataWindow = 3;
+  required Exr_box2i displayWindow = 4;
+  required Exr_lineOrder lineOrder = 5;
+  required float pixelAspectRatio = 6;
+  required Exr_v2f screenWindowCenter = 7;
+  required float screenWindowWidth = 8;
+  required bytes other_attr = 9;
+}
+
+message ExrProto {
+  required ExrHeader header = 1;
+  required bytes scanlines = 2;
+}
+
+// package fuzzer_examples;
\ No newline at end of file
diff --git a/projects/openexr/exr_proto_converter.cc b/projects/openexr/exr_proto_converter.cc
new file mode 100644
index 000000000000..3ecbbaf21b42
--- /dev/null
+++ b/projects/openexr/exr_proto_converter.cc
@@ -0,0 +1,192 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "exr_proto_converter.h"
+
+static void WriteInt(std::stringstream &out, uint32_t x) {
+  //x = __builtin_bswap32(x);
+  out.write((char *)&x, sizeof(x));
+}
+
+static void WriteLong(std::stringstream &out, uint64_t x) {
+  //x = __builtin_bswap64(x);
+  out.write((char *)&x, sizeof(x));
+}
+
+static void WriteFloat(std::stringstream &out, float x) {
+  out.write((char *)&x, sizeof(x));
+}
+
+static void WriteByte(std::stringstream &out, uint8_t x) {
+  out.write((char *)&x, sizeof(x));
+}
+
+static void WriteString(std::stringstream &out, std::string x) {
+  out.write(x.data(), x.size());
+  out.write("\x00", 1);
+}
+
+static void WriteNString(std::stringstream &out, std::string x) {
+  out.write(x.data(), x.size());
+}
+
+static void WriteTypeInfo(std::stringstream &out, std::string name, std::string type, int size) {
+  WriteString(out, name);
+  WriteString(out, type);
+  WriteInt(out, size);
+}
+
+static int CalculateChannelSize (int chan_type) {
+  int size = 0;
+  switch (chan_type % 3) {
+    case 1:
+      size = 2;
+      break;
+    default:
+      size = 4;
+  }
+  return size;
+}
+
+const float MIN_PIXEL_ASPECT_RATIO = 1e-6f;
+const float MAX_PIXEL_ASPECT_RATIO = 1e+6f;
+
+std::string ProtoToExr(const ExrProto &exr_proto) {
+  std::stringstream all;
+  const unsigned char magic[] = {0x76, 0x2f, 0x31, 0x01};
+  all.write((const char*)magic, sizeof(magic));
+
+  // we only support version2 in single flat scanlines for now
+  const unsigned char version[] = {0x02, 0x00, 0x00, 0x00};
+  all.write((const char*)version, sizeof(version));
+
+  auto &header = exr_proto.header();
+
+  std::stringstream channels;
+  std::unordered_set<std::string> used_channels;
+  int pixel_data_size = 0;
+  for (const auto& chan : header.channel_list().channels()) {
+    std::string chname = "G";
+    if (!chan.chname().empty() && std::isprint(static_cast<unsigned char>(chan.chname()[0]))) { 
+      chname = chan.chname();
+    }
+    if (used_channels.find(chname) != used_channels.end()) {
+      continue; // we don't care about this one. bad but at least works
+    } else {
+      used_channels.insert(chname);
+    }
+    WriteString(channels, chname);
+
+    WriteInt(channels, chan.pixel_type() % 3); // UINT, HALF, FLOAT
+    WriteByte(channels, chan.plinear() % 2); // 0 or 1
+    WriteByte(channels, 0);
+    WriteByte(channels, 0);
+    WriteByte(channels, 0);
+    WriteInt(channels, std::max(chan.xsampling(), 1));
+    WriteInt(channels, std::max(chan.ysampling(), 1));
+
+    pixel_data_size += CalculateChannelSize(chan.pixel_type());
+  }
+  WriteTypeInfo(all, "channels", "chlist", channels.str().size() + 1);
+  WriteString(all, channels.str()); // value
+
+  WriteTypeInfo(all, "compression", "compression", 1);
+  WriteByte(all, header.compression().compression() % 10); //value
+
+  // this is the base point
+  auto xmin = header.datawindow().xmin();
+  auto ymin = header.datawindow().ymin();
+
+  // this are the lenght of the sides (h and w) so we compute the other coordinates
+  auto xmax = xmin + std::max(std::min(header.datawindow().w(), 0), 20);
+  auto ymax = ymin + std::max(std::min(header.datawindow().h(), 0), 20);
+
+  WriteTypeInfo(all, "dataWindow", "box2i", 16);
+  WriteInt(all, xmin);
+  WriteInt(all, ymin);
+  WriteInt(all, xmax);
+  WriteInt(all, ymax);
+
+  // we are copying datawindow values to decrease the number of operations
+  // but this also reduce the fuzzed testcases
+  WriteTypeInfo(all, "displayWindow", "box2i", 16);
+  WriteInt(all, xmin);
+  WriteInt(all, ymin);
+  WriteInt(all, xmax);
+  WriteInt(all, ymax);
+
+  WriteTypeInfo(all, "lineOrder", "lineOrder", 1);
+  WriteByte(all, header.lineorder().lineorder() % 3);
+
+  WriteTypeInfo(all, "pixelAspectRatio", "float", 4);
+  auto par = std::max(MIN_PIXEL_ASPECT_RATIO, std::min(header.pixelaspectratio(), MAX_PIXEL_ASPECT_RATIO));
+  if (par == 0.0) { par += 0.1337; }
+  WriteFloat(all, par);
+
+  WriteTypeInfo(all, "screenWindowCenter", "v2f", 8);
+  WriteFloat(all, header.screenwindowcenter().f1());
+  WriteFloat(all, header.screenwindowcenter().f2());
+
+  WriteTypeInfo(all, "screenWindowWidth", "float", 4);
+  WriteFloat(all, header.screenwindowwidth());
+
+  // end of header
+  WriteByte(all, 0);
+
+  auto n_channels = header.channel_list().channels_size();
+  auto lines = (xmax - xmin);
+  auto n_pixels = (ymax - ymin);
+
+  pixel_data_size = pixel_data_size * n_pixels;
+
+  // base_offset contains the header size and the offset table size
+  // the offset table contains 64bit integers for every scanline
+  auto base_offset = static_cast<int>(all.tellp()) + (lines * 8);
+
+  std::stringstream offset_table;
+  std::stringstream scanlines;
+
+  for (int i = 0; i<= lines; i++) {
+    // write the offset for the current line
+    // (4 + 4 + n_pixels * n_channels) for every scanline
+    WriteInt(offset_table, base_offset + (i * (8 + pixel_data_size)));
+
+    // write scanline number
+    WriteInt(scanlines, i);
+    // write pixel data size times pixel number
+    WriteInt(scanlines, pixel_data_size);
+
+    // write the pixel data
+    for (const auto& chan : header.channel_list().channels()) {
+      std::string pixels(n_pixels * CalculateChannelSize(chan.pixel_type()), '\xea');
+      pixels.replace(0, exr_proto.scanlines().size(), exr_proto.scanlines());
+      WriteNString(scanlines, pixels);
+    }
+  }
+
+  WriteNString(all, offset_table.str());
+  WriteByte(all, 255);
+  WriteNString(all, scanlines.str());
+
+  std::string res = all.str();
+  if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
+    // With libFuzzer binary run this to generate a EXR file x.exr:
+    // PROTO_FUZZER_DUMP_PATH=x.exr ./a.out proto-input
+    std::ofstream of(dump_path, std::ios::binary);
+    of.write(res.data(), res.size());
+  }
+  return res;
+}
diff --git a/projects/openexr/exr_proto_converter.h b/projects/openexr/exr_proto_converter.h
new file mode 100644
index 000000000000..ead93c9169ab
--- /dev/null
+++ b/projects/openexr/exr_proto_converter.h
@@ -0,0 +1,31 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef EXR_PROTO_CONVERTER_H
+#define EXR_PROTO_CONVERTER_H
+
+#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
+#include "exr.pb.h"
+
+#include <string>
+#include <sstream>
+#include <fstream>
+#include <cstdlib>
+#include <algorithm>
+
+std::string ProtoToExr(const ExrProto &exr_proto);
+
+#endif // EXR_PROTO_CONVERTER_H
\ No newline at end of file
diff --git a/projects/openexr/exr_proto_fuzzer.cc b/projects/openexr/exr_proto_fuzzer.cc
new file mode 100644
index 000000000000..5205a264febd
--- /dev/null
+++ b/projects/openexr/exr_proto_fuzzer.cc
@@ -0,0 +1,31 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
+#include "exr.pb.h"
+
+#include <ImfNamespace.h>
+#include <ImfCheckFile.h>
+#include <string>
+
+#include "exr_proto_converter.h"
+
+DEFINE_PROTO_FUZZER(const ExrProto &exr) {
+    std::string data = ProtoToExr(exr);
+
+    Imf::checkOpenEXRFile (data.c_str() , data.size() , true , true , true);
+    return;
+}
\ No newline at end of file
diff --git a/projects/openexr/project.yaml b/projects/openexr/project.yaml
index e5093509c5a6..13e686f378a5 100644
--- a/projects/openexr/project.yaml
+++ b/projects/openexr/project.yaml
@@ -5,6 +5,7 @@ auto_ccs:
   - "cbpilm@gmail.com"
   - "security@openexr.org"
   - "kdt3rd@gmail.com"
+  - "research@shielder.it"
 main_repo: 'https://github.com/AcademySoftwareFoundation/openexr'
 
 fuzzing_engines: