diffstat for openexr-3.4.6+ds openexr-3.4.14

 .github/dependabot.yml                                   |   12 
 .github/workflows/analysis_workflow.yml                  |   12 
 .github/workflows/bazel_build.yml                        |   12 
 .github/workflows/ci_freebsd.yml                         |   60 
 .github/workflows/ci_steps.yml                           |   48 
 .github/workflows/ci_workflow.yml                        |   24 
 .github/workflows/ci_workflow_old.yml                    |    3 
 .github/workflows/codeql.yml                             |   14 
 .github/workflows/ossfuzz_workflow.yml                   |    2 
 .github/workflows/python-wheels-publish-test.yml         |  109 -
 .github/workflows/python-wheels-publish.yml              |  105 -
 .github/workflows/python-wheels.yml                      |   86 -
 .github/workflows/release-notice.yml                     |    5 
 .github/workflows/release-sign.yml                       |   22 
 .github/workflows/scorecard.yml                          |    8 
 .github/workflows/snyk-scan-cron.yml                     |    2 
 .github/workflows/website.yml                            |    4 
 .github/workflows/website_preview_link.yml               |   35 
 .gitignore                                               |    1 
 .readthedocs.yml                                         |    3 
 CHANGES.md                                               | 1183 +++++++++++++--
 CONTRIBUTING.md                                          |  429 ++---
 MODULE.bazel                                             |    6 
 README.md                                                |    8 
 SECURITY.md                                              |  159 +-
 cmake/CMakeLists.txt                                     |   12 
 cmake/LibraryDefine.cmake                                |    4 
 cmake/OpenEXRConfig.cmake.in                             |    2 
 cmake/OpenEXRConfig.h.in                                 |    4 
 cmake/OpenEXRSetup.cmake                                 |    5 
 debian/changelog                                         |   38 
 debian/control                                           |    1 
 debian/patches/hack-test-armhf.patch                     |   16 
 debian/patches/hurd-no-sa_restorer.patch                 |   14 
 debian/patches/series                                    |    2 
 debian/rules                                             |   16 
 pyproject.toml                                           |   11 
 share/ci/install_manifest/install_manifest.linux.13.txt  |  206 ++
 share/ci/scripts/install_imath.sh                        |   10 
 share/ci/scripts/install_openjph.sh                      |   10 
 share/util/release.py                                    |  825 ++++++++++
 share/util/release/__init__.py                           |    4 
 share/util/release/_common.py                            |  164 ++
 share/util/release/_security.py                          |  225 ++
 share/util/release/candidate.py                          |   53 
 share/util/release/changes.py                            |  288 +++
 share/util/release/cherry.py                             |   97 +
 share/util/release/draft.py                              |   39 
 share/util/release/log.py                                |   73 
 share/util/release/news.py                               |  131 +
 share/util/release/notes.py                              |   27 
 share/util/release/tag.py                                |   31 
 share/util/vendor_openjph.sh                             |   45 
 src/bin/exrmetrics/exrmetrics.cpp                        |   68 
 src/bin/exrmetrics/exrmetrics.h                          |    1 
 src/bin/exrmetrics/main.cpp                              |   73 
 src/bin/exrmultipart/exrmultipart.cpp                    |   29 
 src/bin/exrmultiview/makeMultiView.cpp                   |    8 
 src/lib/OpenEXR/ImfAcesFile.h                            |    2 
 src/lib/OpenEXR/ImfArray.h                               |   51 
 src/lib/OpenEXR/ImfCRgbaFile.h                           |    5 
 src/lib/OpenEXR/ImfCompression.cpp                       |    4 
 src/lib/OpenEXR/ImfContext.h                             |    2 
 src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp             |   26 
 src/lib/OpenEXR/ImfDeepScanLineInputFile.h               |    2 
 src/lib/OpenEXR/ImfDeepScanLineOutputFile.h              |    2 
 src/lib/OpenEXR/ImfDeepTiledInputFile.cpp                |   20 
 src/lib/OpenEXR/ImfDeepTiledInputFile.h                  |    2 
 src/lib/OpenEXR/ImfDeepTiledOutputFile.h                 |    2 
 src/lib/OpenEXR/ImfIDManifest.cpp                        |   58 
 src/lib/OpenEXR/ImfIO.h                                  |   10 
 src/lib/OpenEXR/ImfInputFile.h                           |    2 
 src/lib/OpenEXR/ImfInputPart.cpp                         |   14 
 src/lib/OpenEXR/ImfInputPart.h                           |    1 
 src/lib/OpenEXR/ImfMisc.h                                |    4 
 src/lib/OpenEXR/ImfMultiPartInputFile.h                  |    4 
 src/lib/OpenEXR/ImfMultiPartOutputFile.h                 |    2 
 src/lib/OpenEXR/ImfMultiView.cpp                         |    2 
 src/lib/OpenEXR/ImfName.h                                |    1 
 src/lib/OpenEXR/ImfOutputFile.h                          |    2 
 src/lib/OpenEXR/ImfRgbaFile.cpp                          |    6 
 src/lib/OpenEXR/ImfRgbaFile.h                            |    2 
 src/lib/OpenEXR/ImfScanLineInputFile.cpp                 |    3 
 src/lib/OpenEXR/ImfScanLineInputFile.h                   |    3 
 src/lib/OpenEXR/ImfSimd.h                                |   14 
 src/lib/OpenEXR/ImfStandardAttributes.cpp                |   16 
 src/lib/OpenEXR/ImfStdIO.h                               |    4 
 src/lib/OpenEXR/ImfTestFile.h                            |    4 
 src/lib/OpenEXR/ImfTiledInputFile.h                      |    2 
 src/lib/OpenEXR/ImfTiledMisc.cpp                         |    2 
 src/lib/OpenEXR/ImfTiledOutputFile.h                     |    2 
 src/lib/OpenEXR/ImfTiledRgbaFile.cpp                     |    6 
 src/lib/OpenEXR/ImfTiledRgbaFile.h                       |    2 
 src/lib/OpenEXR/ImfZip.cpp                               |    6 
 src/lib/OpenEXRCore/CMakeLists.txt                       |   14 
 src/lib/OpenEXRCore/bytes.c                              |   18 
 src/lib/OpenEXRCore/channel_list.c                       |    7 
 src/lib/OpenEXRCore/chunk.c                              |   25 
 src/lib/OpenEXRCore/compression.c                        |    6 
 src/lib/OpenEXRCore/decoding.c                           |   42 
 src/lib/OpenEXRCore/encoding.c                           |   20 
 src/lib/OpenEXRCore/internal_b44.c                       |   33 
 src/lib/OpenEXRCore/internal_cpuid.h                     |    4 
 src/lib/OpenEXRCore/internal_dwa_compressor.h            |   87 -
 src/lib/OpenEXRCore/internal_dwa_decoder.h               |   21 
 src/lib/OpenEXRCore/internal_dwa_encoder.h               |    8 
 src/lib/OpenEXRCore/internal_dwa_simd.h                  |   52 
 src/lib/OpenEXRCore/internal_ht.cpp                      |   89 -
 src/lib/OpenEXRCore/internal_ht_common.cpp               |   28 
 src/lib/OpenEXRCore/internal_ht_common.h                 |   54 
 src/lib/OpenEXRCore/internal_piz.c                       |   39 
 src/lib/OpenEXRCore/internal_pxr24.c                     |   10 
 src/lib/OpenEXRCore/internal_structs.c                   |   59 
 src/lib/OpenEXRCore/internal_thread.h                    |   29 
 src/lib/OpenEXRCore/internal_util.h                      |   29 
 src/lib/OpenEXRCore/internal_zip.c                       |   29 
 src/lib/OpenEXRCore/openexr_version.h                    |    2 
 src/lib/OpenEXRCore/parse_header.c                       |    4 
 src/lib/OpenEXRCore/part.c                               |   10 
 src/lib/OpenEXRCore/part_attr.c                          |   69 
 src/lib/OpenEXRCore/preview.c                            |   18 
 src/lib/OpenEXRCore/string.c                             |   28 
 src/lib/OpenEXRCore/unpack.c                             |   74 
 src/lib/OpenEXRUtil/ImfCheckFile.h                       |    2 
 src/lib/OpenEXRUtil/ImfDeepImageChannel.h                |    4 
 src/lib/OpenEXRUtil/ImfDeepImageIO.h                     |    2 
 src/lib/OpenEXRUtil/ImfFlatImageChannel.h                |    4 
 src/lib/OpenEXRUtil/ImfFlatImageIO.h                     |    2 
 src/lib/OpenEXRUtil/ImfImage.cpp                         |  143 +
 src/lib/OpenEXRUtil/ImfImageChannel.cpp                  |   22 
 src/lib/OpenEXRUtil/ImfImageIO.h                         |    2 
 src/lib/OpenEXRUtil/ImfImageLevel.cpp                    |    5 
 src/lib/OpenEXRUtil/ImfSampleCountChannel.cpp            |   23 
 src/lib/OpenEXRUtil/ImfSampleCountChannel.h              |    2 
 src/test/OpenEXRCoreTest/CMakeLists.txt                  |    2 
 src/test/OpenEXRCoreTest/base_units.cpp                  |    2 
 src/test/OpenEXRCoreTest/compression.cpp                 |   76 
 src/test/OpenEXRCoreTest/compression.h                   |    1 
 src/test/OpenEXRCoreTest/general_attr.cpp                |   85 +
 src/test/OpenEXRCoreTest/main.cpp                        |    2 
 src/test/OpenEXRCoreTest/read.cpp                        |  118 +
 src/test/OpenEXRCoreTest/read.h                          |    2 
 src/test/OpenEXRCoreTest/write.cpp                       |   29 
 src/test/OpenEXRTest/CMakeLists.txt                      |    3 
 src/test/OpenEXRTest/TestUtilFStream.h                   |   20 
 src/test/OpenEXRTest/main.cpp                            |    2 
 src/test/OpenEXRTest/testCompressionApi.cpp              |    4 
 src/test/OpenEXRTest/testExistingStreams.cpp             |   49 
 src/test/OpenEXRTest/testMagic.cpp                       |   92 +
 src/test/OpenEXRTest/testMultiView.cpp                   |    7 
 src/test/OpenEXRTest/testOptimized.cpp                   |  607 -------
 src/test/OpenEXRTest/testOptimized.h                     |    8 
 src/test/OpenEXRTest/testOptimizedInterleavePatterns.cpp |   29 
 src/test/OpenEXRTest/testStandardAttributes.cpp          |   17 
 src/test/OpenEXRUtilTest/CMakeLists.txt                  |    3 
 src/test/OpenEXRUtilTest/main.cpp                        |    2 
 src/test/OpenEXRUtilTest/testDeepImage.cpp               |  103 +
 src/test/OpenEXRUtilTest/testFlatImage.cpp               |   25 
 src/test/OpenEXRUtilTest/testImageChannel.cpp            |   82 +
 src/test/OpenEXRUtilTest/testImageChannel.h              |    8 
 src/test/bin/test_exrmetrics.py                          |    5 
 src/wrappers/python/PyOpenEXR.cpp                        |  841 ++++++----
 src/wrappers/python/PyOpenEXR.h                          |    8 
 src/wrappers/python/tests/test_deep.py                   |  111 +
 src/wrappers/python/tests/test_exceptions.py             |   20 
 src/wrappers/python/tests/test_rgba.py                   |   33 
 src/wrappers/python/tests/test_unittest.py               |  254 +++
 src/wrappers/python/tests/unfinished_multipart.exr       |binary
 website/ReadingAndWritingImageFiles.rst                  |   53 
 website/TechnicalIntroduction.rst                        |   37 
 website/bin/exrmetrics.rst                               |    5 
 website/index.rst                                        |   21 
 website/install.rst                                      |   80 +
 website/latest_news_title.rst                            |    8 
 website/news.rst                                         |  282 +++
 website/requirements-lock.txt                            |  323 ++++
 website/requirements.txt                                 |   11 
 website/src/exrreader/CMakeLists.txt                     |    1 
 website/src/exrreader_max/CMakeLists.txt                 |   12 
 website/src/exrreader_max/exrreader_max.cpp              |  100 +
 180 files changed, 7842 insertions(+), 2034 deletions(-)

diff -Nru openexr-3.4.6+ds/CHANGES.md openexr-3.4.14/CHANGES.md
--- openexr-3.4.6+ds/CHANGES.md	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/CHANGES.md	2026-08-05 04:29:55.000000000 +0300
@@ -3,6 +3,14 @@
 
 # OpenEXR Release Notes
 
+* [Version 3.4.14](#version-3414-august-6-2026) August 6, 2026
+* [Version 3.4.13](#version-3413-june-19-2026) June 19, 2026
+* [Version 3.4.12](#version-3412-may-24-2026) May 24, 2026
+* [Version 3.4.11](#version-3411-april-29-2026) April 29, 2026
+* [Version 3.4.10](#version-3410-april-17-2026) April 17, 2026
+* [Version 3.4.9](#version-349-april-3-2026) April 3, 2026
+* [Version 3.4.8](#version-348-march-26-2026) March 26, 2026
+* [Version 3.4.7](#version-347-march-15-2026) March 15, 2026
 * [Version 3.4.6](#version-346-march-1-2026) March 1, 2026
 * [Version 3.4.5](#version-345-february-21-2026) February 21, 2026
 * [Version 3.4.4](#version-344-november-19-2025) November 19, 2025
@@ -10,6 +18,10 @@
 * [Version 3.4.2](#version-342-october-15-2025) October 15, 2025
 * [Version 3.4.1](#version-341-october-8-2025) October 8, 2025
 * [Version 3.4.0](#version-340-september-5-2025) September 5, 2025
+* [Version 3.3.11](#version-3311-april-29-2026) April 29, 2026
+* [Version 3.3.10](#version-339-april-17-2026) April 17, 2026
+* [Version 3.3.9](#version-339-april-4-2026) April 4, 2026
+* [Version 3.3.8](#version-338-march-1-2026) March 1, 2026
 * [Version 3.3.7](#version-337-february-19-2026) February 19, 2026
 * [Version 3.3.6](#version-336-november-4-2025) November 5, 2025
 * [Version 3.3.5](#version-335-july-26-2025) July 26, 2025
@@ -18,6 +30,11 @@
 * [Version 3.3.2](#version-332-november-11-2024) November 11, 2024
 * [Version 3.3.1](#version-331-october-8-2024) October 8, 2024
 * [Version 3.3.0](#version-330-september-30-2024) September 30, 2024
+* [Version 3.2.9](#version-329-april-29-2026) April 29, 2026
+* [Version 3.2.8](#version-328-april-17-2026) April 17, 2026
+* [Version 3.2.7](#version-327-april-3-2026) April 3, 2026
+* [Version 3.2.6](#version-326-march-1-2026) March 1, 2026
+* [Version 3.2.5](#version-325-november-4-2025) November 4, 2025
 * [Version 3.2.4](#version-324-march-26-2024) March 26, 2024
 * [Version 3.2.3](#version-323-march-6-2024) March 6, 2024
 * [Version 3.2.2](#version-322-february-11-2024) February 11, 2024
@@ -89,6 +106,723 @@
 * [Version 1.0.1](#version-101)
 * [Version 1.0](#version-10)
 
+## Version 3.4.14 (August 6, 2026)
+
+v3.4.14 is a security-focused patch release. It fixes 15 CVEs plus a
+broad set of additional hardening changes uncovered by the same
+fuzzing/audit effort.
+
+For each of these vulnerabilities, an attacker's vector is a
+maliciously crafted `.exr` file that must be opened by a victim,
+whether through the OpenEXR/OpenEXRUtil C++ libraries, the
+command-line tools (`exrmetrics`, `exrmultiview`, `exrmultipart`), or
+the PyOpenEXR Python bindings. The primary flaw for most of the CVEs
+is **memory corruption** — heap buffer overflows and out-of-bounds
+reads/writes — which at minimum crashes the reading process (denial of
+service) and in several cases could plausibly be leveraged for
+information disclosure or, in the worst cases, arbitrary code
+execution.
+
+No user interaction beyond opening the file is required, so any
+pipeline, service, or application that decodes untrusted or
+third-party EXR files should treat this as a priority
+upgrade. Severity generally ranges from **moderate** (crash-only, or
+requiring an uncommon build configuration) to **high** (heap overflow
+reachable with a small, easily-crafted file on common configurations).
+
+The individual vulnerabilities fall into four broad groups:
+
+* **PyOpenEXR RGB-channel-coalescing bugs**
+  ([CVE-2026-68514](https://www.cve.org/CVERecord?id=CVE-2026-68514),
+  [CVE-2026-68513](https://www.cve.org/CVERecord?id=CVE-2026-68513),
+  [CVE-2026-62986](https://www.cve.org/CVERecord?id=CVE-2026-62986),
+  [CVE-2026-61703](https://www.cve.org/CVERecord?id=CVE-2026-61703)).
+  When the Python bindings combine per-channel data (e.g. `left.R`,
+  `left.G`, `left.B`) into a single coalesced RGB array, conflicting or
+  mismatched channel names/types were not fully validated, which could
+  undersize the destination NumPy buffer. The result is a heap buffer
+  overflow on read, or, in the deep-image case, disclosure of
+  uninitialized ("stale") heap memory through the returned array. This
+  affects only code paths that read files with `separate_channels=False`
+  (the default for RGB coalescing).
+
+* **Integer-overflow-driven heap overflows on 32-bit (ILP32) builds**
+  ([CVE-2026-59985](https://www.cve.org/CVERecord?id=CVE-2026-59985),
+  [CVE-2026-59984](https://www.cve.org/CVERecord?id=CVE-2026-59984),
+  [CVE-2026-59983](https://www.cve.org/CVERecord?id=CVE-2026-59983),
+  [CVE-2026-59982](https://www.cve.org/CVERecord?id=CVE-2026-59982),
+  [CVE-2026-59981](https://www.cve.org/CVERecord?id=CVE-2026-59981),
+  [CVE-2026-59189](https://www.cve.org/CVERecord?id=CVE-2026-59189),
+  [CVE-2026-59186](https://www.cve.org/CVERecord?id=CVE-2026-59186)).
+  On platforms where `size_t`/`int` are 32 bits, buffer sizes computed
+  from attacker-controlled header fields (dimensions, sample counts,
+  tile sizes) could overflow before an allocation or bounds check,
+  yielding an undersized buffer and a subsequent heap out-of-bounds
+  read or write during RLE, B44/B44A, or DWAA decompression, deep
+  sample-count-table decoding, or large-tile handling. These do not
+  affect typical 64-bit desktop/server builds, but are significant for
+  32-bit Linux, embedded, and some mobile/CI targets.
+
+* **Heap out-of-bounds access in `OpenEXRUtil` and the command-line
+  tools with non-default data windows**
+  ([CVE-2026-59981](https://www.cve.org/CVERecord?id=CVE-2026-59981),
+  [CVE-2026-59189](https://www.cve.org/CVERecord?id=CVE-2026-59189),
+  [CVE-2026-59187](https://www.cve.org/CVERecord?id=CVE-2026-59187),
+  [CVE-2026-59186](https://www.cve.org/CVERecord?id=CVE-2026-59186),
+  [CVE-2026-59184](https://www.cve.org/CVERecord?id=CVE-2026-59184)).
+  `FlatImageChannel`/`DeepImageChannel`/`SampleCountChannel` row
+  addressing, and the deep-pixel path in `exrmetrics`, assumed a
+  data window originating at (0, 0). Crafted files with a nonzero data
+  window origin, or subsampled channels, caused row-address
+  computations to land outside the allocated buffer, producing a heap
+  read or write out of bounds.
+
+* **Crashes from malformed metadata (denial of service)**
+  ([CVE-2026-61555](https://www.cve.org/CVERecord?id=CVE-2026-61555),
+  [CVE-2026-59183](https://www.cve.org/CVERecord?id=CVE-2026-59183)).
+  An empty `multiView` attribute could crash `viewFromChannelName()`,
+  and a signed integer overflow while decoding deep tile chunks could
+  lead to an out-of-bounds access. Both are reachable simply by opening
+  a crafted file and result in a crash rather than corrupting memory
+  in an attacker-controlled way.
+
+Beyond the formally-numbered CVEs, this release includes a large batch
+of fixes found by the same audit/fuzzing effort:
+
+* reject truncated or short compressed streams before unpacking
+  (zlib, RLE, DWA, `NO_COMPRESSION`) instead of reading past the end
+  of the input;
+
+* reject oversized allocation requests instead of overflowing size
+  computations (`Array2D`, exrmetrics, exrmultipart channel stores,
+  DWAA/B44 scratch buffers);
+
+* add `NULL`-pointer checks in several `OpenEXRCore` C-API setters
+  (channel list duplication, preview image, `bytes` attributes);
+
+* fix a `Name::operator=` truncation/termination bug;
+
+* close a double-free warning in `ImfTiledMisc`.
+
+This release also fixes a long-standing correctness bug (not a
+security issue) in byte-swapping float-vector attributes that caused
+incorrect results and test failures on big-endian s390x builds.
+
+This release also bumps the vendored OpenJPH version to 0.31.0.
+
+CVEs addressed:
+
+* [CVE-2026-68514](https://www.cve.org/CVERecord?id=CVE-2026-68514)
+  PyOpenEXR deep prefixed literal RGB key collision heap buffer overflow
+* [CVE-2026-68513](https://www.cve.org/CVERecord?id=CVE-2026-68513)
+  PyOpenEXR prefixed literal RGB key collision heap buffer overflow
+* [CVE-2026-62986](https://www.cve.org/CVERecord?id=CVE-2026-62986)
+  PyOpenEXR deep prefixed RGB stale lane disclosure
+* [CVE-2026-61703](https://www.cve.org/CVERecord?id=CVE-2026-61703)
+  PyOpenEXR deep mixed RGB heap buffer overflow
+* [CVE-2026-61555](https://www.cve.org/CVERecord?id=CVE-2026-61555)
+  empty multiView viewFromChannelName file crash
+* [CVE-2026-59985](https://www.cve.org/CVERecord?id=CVE-2026-59985)
+  ILP32 OpenEXRCore RLE decode heap OOB read DoS
+* [CVE-2026-59984](https://www.cve.org/CVERecord?id=CVE-2026-59984)
+  ILP32 B44 InputFile decode scratch buffer overflow
+* [CVE-2026-59983](https://www.cve.org/CVERecord?id=CVE-2026-59983)
+  ILP32 DeepTiledInputFile sample count table decode OOB read
+* [CVE-2026-59982](https://www.cve.org/CVERecord?id=CVE-2026-59982)
+  ILP32 DWAA InputFile packed AC buffer overflow
+* [CVE-2026-59981](https://www.cve.org/CVERecord?id=CVE-2026-59981)
+  OpenEXRUtil SampleCountChannel row nonzero dataWindow heap OOB read
+* [CVE-2026-59189](https://www.cve.org/CVERecord?id=CVE-2026-59189)
+  OpenEXRUtil DeepImageChannel row nonzero dataWindow heap OOB read
+* [CVE-2026-59187](https://www.cve.org/CVERecord?id=CVE-2026-59187)
+  OpenEXR exrmetrics deep pixelmode heap buffer overflow
+* [CVE-2026-59186](https://www.cve.org/CVERecord?id=CVE-2026-59186)
+  OpenEXR ILP32 TiledRgbaInputFile large tile Array2D heap OOB write
+* [CVE-2026-59184](https://www.cve.org/CVERecord?id=CVE-2026-59184)
+  OpenEXRUtil FlatImageChannel row nonzero dataWindow heap OOB write
+* [CVE-2026-59183](https://www.cve.org/CVERecord?id=CVE-2026-59183)
+  Signed Integer Overflow Leading to Out-of-Bounds Memory Access in Deep Tile Decoding
+
+### Merged Pull Requests
+
+* [2569](https://github.com/AcademySoftwareFoundation/openexr/pull/2569)
+  Fix MSYS2 MINGW32 CI: build Imath/OpenJPH from source instead of pacboy
+* [2567](https://github.com/AcademySoftwareFoundation/openexr/pull/2567)
+  Fix handling of subsampled channels in PyOpenEXR
+* [2566](https://github.com/AcademySoftwareFoundation/openexr/pull/2566)
+  Reject DWA RLE and UNKNOWN streams shorter than required channel data.
+* [2565](https://github.com/AcademySoftwareFoundation/openexr/pull/2565)
+  fix signed/unsigned wraparound in HTJ2K planar decode row loop
+* [2550](https://github.com/AcademySoftwareFoundation/openexr/pull/2550)
+  zero-fill deep sample-count table for zero-length compressed input
+* [2548](https://github.com/AcademySoftwareFoundation/openexr/pull/2548)
+  Reject ``NO_COMPRESSION`` scanline chunks with short packed size.
+* [2547](https://github.com/AcademySoftwareFoundation/openexr/pull/2547)
+  Reject short zlib inflates before pixel unpack.
+* [2543](https://github.com/AcademySoftwareFoundation/openexr/pull/2543)
+  Fix subsampled row addressing in ``generic_unpack`` for multi-line chunks
+* [2541](https://github.com/AcademySoftwareFoundation/openexr/pull/2541)
+  Reject oversized channelstore allocations in exrmultipart convert
+* [2540](https://github.com/AcademySoftwareFoundation/openexr/pull/2540)
+  Reject oversized Array2D allocations on ILP32
+* [2539](https://github.com/AcademySoftwareFoundation/openexr/pull/2539)
+  Reject oversized exrmetrics vector allocations on ILP32
+* [2535](https://github.com/AcademySoftwareFoundation/openexr/pull/2535)
+  Fix missing ``INT_MAX`` cap on deep scanline ``sample_count_table_size``
+* [2530](https://github.com/AcademySoftwareFoundation/openexr/pull/2530)
+  Fix exrmultiview heap OOB when union dataWindow misaligns with subsampling
+* [2529](https://github.com/AcademySoftwareFoundation/openexr/pull/2529)
+  Refuse to coalesce RGB channels when there's a conflicting channel name
+* [2528](https://github.com/AcademySoftwareFoundation/openexr/pull/2528)
+  Fix exrmetrics heap OOB on subsampled scanlines
+* [2527](https://github.com/AcademySoftwareFoundation/openexr/pull/2527)
+  Upgrade manylinux cibuildwheel images to manylinux_2_28
+* [2522](https://github.com/AcademySoftwareFoundation/openexr/pull/2522)
+  Fix name comparison in deep prefixed RGB channel coalescing
+* [2516](https://github.com/AcademySoftwareFoundation/openexr/pull/2516)
+  htj2k: Check for duplication ``file_index`` values when reading file header
+* [2510](https://github.com/AcademySoftwareFoundation/openexr/pull/2510)
+  Core: byte-swap the float vector data, not the attribute struct (fixes big-endian crash)
+* [2509](https://github.com/AcademySoftwareFoundation/openexr/pull/2509)
+  Return native-order deep sample counts from ``exr_read_deep_chunk``
+* [2504](https://github.com/AcademySoftwareFoundation/openexr/pull/2504)
+  fix --help message in exrmetrics
+* [2503](https://github.com/AcademySoftwareFoundation/openexr/pull/2503)
+  prevent warning of potential double-free in ImfTiledMisc
+* [2502](https://github.com/AcademySoftwareFoundation/openexr/pull/2502)
+  Reject mixed pixel types when coalescing RGB channels in PyOpenEXR.
+* [2500](https://github.com/AcademySoftwareFoundation/openexr/pull/2500)
+  Fix ``Name::operator=`` to null-terminate long strings
+* [2498](https://github.com/AcademySoftwareFoundation/openexr/pull/2498)
+  Avoid memory allocation in idmanifest parsing
+* [2496](https://github.com/AcademySoftwareFoundation/openexr/pull/2496)
+  Fix empty multiView crash in ``viewFromChannelName()``
+* [2494](https://github.com/AcademySoftwareFoundation/openexr/pull/2494)
+  Reject oversized DWAA buffer allocations on ILP32
+* [2493](https://github.com/AcademySoftwareFoundation/openexr/pull/2493)
+  Fix ILP32 unpacked buffer size truncation in RLE decode
+* [2492](https://github.com/AcademySoftwareFoundation/openexr/pull/2492)
+  Fix ILP32 deep sample-count table size overflow in decoding
+* [2491](https://github.com/AcademySoftwareFoundation/openexr/pull/2491)
+  Fix ILP32 B44/B44A scratch buffer overflow in decode
+* [2490](https://github.com/AcademySoftwareFoundation/openexr/pull/2490)
+  Reject negative pixelDataSize in ``rawPixelDataToBuffer()``
+* [2488](https://github.com/AcademySoftwareFoundation/openexr/pull/2488)
+  Fix OpenEXRUtil ``row()`` OOB read with non-zero data window origin
+* [2487](https://github.com/AcademySoftwareFoundation/openexr/pull/2487)
+  Fix deep pixelmode heap buffer overflow in exrmetrics
+* [2486](https://github.com/AcademySoftwareFoundation/openexr/pull/2486)
+  Fix integer overflow in ``Array2D::resizeErase()`` on ILP32/LLP64 builds
+* [2484](https://github.com/AcademySoftwareFoundation/openexr/pull/2484)
+  Fix signed integer overflow in ``unpack_sample_table()``
+* [2473](https://github.com/AcademySoftwareFoundation/openexr/pull/2473)
+  Reject NULL channel list entries in ``exr_attr_chlist_duplicate()``
+* [2469](https://github.com/AcademySoftwareFoundation/openexr/pull/2469)
+  Reject NULL preview rgba data in ``exr_attr_set_preview()``
+
+### Merged Workflow Pull Requests
+
+* [2568](https://github.com/AcademySoftwareFoundation/openexr/pull/2568)
+  Bump the codeql group with 3 updates
+* [2564](https://github.com/AcademySoftwareFoundation/openexr/pull/2564)
+  Bump pypa/gh-action-pypi-publish from 1.14.1 to 1.14.2
+* [2563](https://github.com/AcademySoftwareFoundation/openexr/pull/2563)
+  Bump sigstore/gh-action-sigstore-python from 3.4.0 to 3.5.0
+* [2558](https://github.com/AcademySoftwareFoundation/openexr/pull/2558)
+  Bump bazel_skylib from 1.9.0 to 1.9.2
+* [2557](https://github.com/AcademySoftwareFoundation/openexr/pull/2557)
+  Bump ossf/scorecard-action from 2.4.3 to 2.4.4
+* [2556](https://github.com/AcademySoftwareFoundation/openexr/pull/2556)
+  Bump the codeql group with 3 updates
+* [2555](https://github.com/AcademySoftwareFoundation/openexr/pull/2555)
+  Bump actions/checkout from 7.0.0 to 7.0.1
+* [2554](https://github.com/AcademySoftwareFoundation/openexr/pull/2554)
+  Bump actions/setup-python from 6.3.0 to 7.0.0
+* [2553](https://github.com/AcademySoftwareFoundation/openexr/pull/2553)
+  Bump pypa/gh-action-pypi-publish from 1.14.0 to 1.14.1
+* [2552](https://github.com/AcademySoftwareFoundation/openexr/pull/2552)
+  Bump vmactions/freebsd-vm from 1.5.0 to 1.5.2
+* [2551](https://github.com/AcademySoftwareFoundation/openexr/pull/2551)
+  Bump the codeql group with 3 updates
+* [2538](https://github.com/AcademySoftwareFoundation/openexr/pull/2538)
+  Bump rules_cc from 0.2.20 to 0.2.22
+* [2514](https://github.com/AcademySoftwareFoundation/openexr/pull/2514)
+  Bump actions/cache from 6.0.0 to 6.1.0
+* [2513](https://github.com/AcademySoftwareFoundation/openexr/pull/2513)
+  Bump vmactions/freebsd-vm from 1.4.8 to 1.5.0
+* [2511](https://github.com/AcademySoftwareFoundation/openexr/pull/2511)
+  Bump rules_cc from 0.2.19 to 0.2.20
+* [2506](https://github.com/AcademySoftwareFoundation/openexr/pull/2506)
+  Bump actions/cache from 5.0.5 to 6.0.0
+* [2505](https://github.com/AcademySoftwareFoundation/openexr/pull/2505)
+  Bump actions/setup-python from 6.2.0 to 6.3.0
+* [2495](https://github.com/AcademySoftwareFoundation/openexr/pull/2495)
+  Bump vmactions/freebsd-vm from 1.4.6 to 1.4.8
+* [2483](https://github.com/AcademySoftwareFoundation/openexr/pull/2483)
+  Fix CI-old workflow failure due to Node 24 on old VFX platform containers
+* [2481](https://github.com/AcademySoftwareFoundation/openexr/pull/2481)
+  Bump actions/checkout from 6.0.3 to 7.0.0
+* [2460](https://github.com/AcademySoftwareFoundation/openexr/pull/2460)
+  Bump rules_cc from 0.2.18 to 0.2.19
+* [2386](https://github.com/AcademySoftwareFoundation/openexr/pull/2386)
+  Bump rules_cc from 0.2.17 to 0.2.18
+
+## Version 3.4.13 (June 19, 2026)
+
+Patch release that addresses several bugs and security
+vulnerabilities.
+
+* :bug: Fix a regression introduced in v3.4.11 in decoding of DWAA compression
+* :bug: Fix to handling deep images and very large images with the OpenEXRUtil library
+* :bug: Fix initiliazation issue in B44A decoding 
+* :bug: Validate HTJ2K chunk header length before decode
+* :hammer_and_wrench: Fix when building statically and using the vendored OpenJPH library
+
+For the python module:
+
+* :snake: :sparkles: Support NumPy scalar values Box2i and V2f tuple bindings
+
+This release addresses the following security vulnerabilities:
+
+* [CVE-2026-55373](https://www.cve.org/CVERecord?id=CVE-2026-55373)
+  OpenEXRUtil `SampleCountChannel` `endEdit()` can loop forever on `UINT_MAX` sample counts
+* [CVE-2026-55371](https://www.cve.org/CVERecord?id=CVE-2026-55371)
+  OpenEXRCore `exr_attr_set_bytes()` accepts NULL `type_hint` with positive `hint_length`
+* [CVE-2026-55059](https://www.cve.org/CVERecord?id=CVE-2026-55059)
+  OpenEXRUtil `SampleCountChannel` row setter heap out-of-bounds write
+* [CVE-2026-54920](https://www.cve.org/CVERecord?id=CVE-2026-54920)
+  Integer Overflow and Use of Uninitialized Pointer leading to Invalid Delete in OpenEXRUtil Image Resize
+* [CVE-2026-53532](https://www.cve.org/CVERecord?id=CVE-2026-53532)
+  Unhandled assert abort in HTJ2K decoder via crafted QCD marker (DoS)
+  
+
+### Security
+
+This release addresses the following security vulnerabilities:
+
+* [CVE-2026-55373](https://www.cve.org/CVERecord?id=CVE-2026-55373)
+  OpenEXRUtil SampleCountChannel endEdit() can loop forever on UINT_MAX sample counts
+* [CVE-2026-55371](https://www.cve.org/CVERecord?id=CVE-2026-55371)
+  OpenEXRCore exr_attr_set_bytes() accepts NULL type_hint with positive hint_length
+* [CVE-2026-55059](https://www.cve.org/CVERecord?id=CVE-2026-55059)
+  OpenEXRUtil SampleCountChannel row setter heap out-of-bounds write
+* [CVE-2026-54920](https://www.cve.org/CVERecord?id=CVE-2026-54920)
+  Integer Overflow and Use of Uninitialized Pointer leading to Invalid Delete in OpenEXRUtil Image Resize
+
+### Merged Pull Requests
+
+* [2476](https://github.com/AcademySoftwareFoundation/openexr/pull/2476)
+  Fix the DWAA size checks regression
+* [2472](https://github.com/AcademySoftwareFoundation/openexr/pull/2472)
+  Unlock write context in exr_get_chunk_table_offset() return paths
+* [2471](https://github.com/AcademySoftwareFoundation/openexr/pull/2471)
+  Add section on CVE Assignment to SECURITY.md
+* [2470](https://github.com/AcademySoftwareFoundation/openexr/pull/2470)
+  Reject NULL bytes type_hint and data in exr_attr_bytes_create()
+* [2468](https://github.com/AcademySoftwareFoundation/openexr/pull/2468)
+  Fix infinite loop in SampleCountChannel roundListSizeUp()
+* [2466](https://github.com/AcademySoftwareFoundation/openexr/pull/2466)
+  Fix install manifest for linux build 13
+* [2464](https://github.com/AcademySoftwareFoundation/openexr/pull/2464)
+  Fix heap out-of-bounds write in SampleCountChannel row setter.
+* [2463](https://github.com/AcademySoftwareFoundation/openexr/pull/2463)
+  Fix integer overflow and invalid delete in OpenEXRUtil Image::resize()
+* [2451](https://github.com/AcademySoftwareFoundation/openexr/pull/2451)
+  Initialize B44 tables before B44A decode
+* [2445](https://github.com/AcademySoftwareFoundation/openexr/pull/2445)
+  Fix: Allow NumPy scalar values in Box2i and V2f tuple bindings
+* [2444](https://github.com/AcademySoftwareFoundation/openexr/pull/2444)
+  Fix vendored OpenJPH for static builds
+* [2442](https://github.com/AcademySoftwareFoundation/openexr/pull/2442)
+  Bugfix: Fix interger overflows
+* [2438](https://github.com/AcademySoftwareFoundation/openexr/pull/2438)
+  Break release.py into separate small scripts for each step
+* [2436](https://github.com/AcademySoftwareFoundation/openexr/pull/2436)
+  Release notes and news for v3.4.12
+* [2434](https://github.com/AcademySoftwareFoundation/openexr/pull/2434)
+  Validate HTJ2K chunk header length before decode
+* [2433](https://github.com/AcademySoftwareFoundation/openexr/pull/2433)
+  Tighten python publish workflow security/efficiency
+* [2430](https://github.com/AcademySoftwareFoundation/openexr/pull/2430)
+  Pin idna>=3.15 in website/requirements.txt
+
+### Merged Workflow Pull Requests
+
+* [2479](https://github.com/AcademySoftwareFoundation/openexr/pull/2479)
+  Split macOS wheel arches across CI runners
+* [2474](https://github.com/AcademySoftwareFoundation/openexr/pull/2474)
+  Bump msys2/setup-msys2 from 2.31.1 to 2.32.0
+* [2467](https://github.com/AcademySoftwareFoundation/openexr/pull/2467)
+  Bump sigstore/gh-action-sigstore-python from 3.3.0 to 3.4.0
+* [2462](https://github.com/AcademySoftwareFoundation/openexr/pull/2462)
+  Update idna requirement from >=3.17 to >=3.18 in /website
+* [2450](https://github.com/AcademySoftwareFoundation/openexr/pull/2450)
+  Update idna requirement from >=3.15 to >=3.17 in /website
+* [2449](https://github.com/AcademySoftwareFoundation/openexr/pull/2449)
+  Bump vmactions/freebsd-vm from 1.4.5 to 1.4.6
+* [2437](https://github.com/AcademySoftwareFoundation/openexr/pull/2437)
+  Bump github/codeql-action from 4.35.4 to 4.36.0
+* [2433](git@github.com:AcademySoftwareFoundation/openexr/pull/2433)
+  Tighten python publish workflow security/efficiency
+* [2430](git@github.com:AcademySoftwareFoundation/openexr/pull/2430)
+  Pin idna>=3.15 in website/requirements.txt
+
+## Version 3.4.12 (May 24, 2026)
+
+Patch release that addresses several bugs and security
+vulnerabilities.
+
+* :bug: Fix several minor memory leaks recovering from reading invalid
+  files.
+* :bug: The compressor API incorrectly identfied `HTJ2K` and `HTJ2K256` as
+  lossy; they are lossles.
+* :bug: Fix CMake AVX feature detection that caused DWA SIMD code to fail on
+  certain architectures.
+* :warning: The `WidenFilename` utility function is marked as deprecated, to be
+  removed in a future release.
+* :sparkles: `exrmetrics` now print the on-disk size of the data portion of each
+  part. Useful for determining compression impact on part data
+
+For the python module:
+
+* :snake: :bug: Reject files where the dataWindows does not match the
+  pixel array dimensions.
+* :snake: :sparkles: Support NumPy float vector attributes
+* :snake: :sparkles: Reading now skips over invalid parts, returns the valid parts only.
+* :snake: :book: Doc strings have proper indentation
+
+This release addresses the following security vulnerabilities:
+
+* [CVE-2026-45696](https://www.cve.org/CVERecord?id=CVE-2026-45696)
+OpenEXR `ht_undo_impl` heap-buffer-overflow READ via
+codestream/channel width mismatch in HTJ2K decode
+* [CVE-2026-44663](https://www.cve.org/CVERecord?id=CVE-2026-44663)
+Integer overflow in HTJ2K decoder ( `ht_undo_impl` ) leading to
+heap-buffer-overflow
+* [OSS-fuzz 512895184](https://issues.oss-fuzz.com/issues/512895184) 
+Null-dereference WRITE in `Imf_4_0::TileProcess::run_decode`
+* [OSS-fuzz 512314697](https://issues.oss-fuzz.com/issues/512314697)
+Direct-leak in `internal_exr_add_part`
+* [OSS-fuzz 508362159](https://issues.oss-fuzz.com/issues/508362159)
+Heap-buffer-overflow in `DwaCompressor_uncompress`
+* [OSS-fuzz 507413960](https://issues.oss-fuzz.com/issues/507413960)
+Heap-buffer-overflow in `generic_unpack`
+
+<<<<<<< HEAD
+=======
+
+>>>>>>> c9519c7c (Release notes and news for v3.4.12 (#2436))
+### Merged Pull Requests
+
+* [2424](https://github.com/AcademySoftwareFoundation/openexr/pull/2424)
+exrmetrics print disk size of part
+* [2423](https://github.com/AcademySoftwareFoundation/openexr/pull/2423)
+Fix computation of chunk height
+* [2422](https://github.com/AcademySoftwareFoundation/openexr/pull/2422)
+Fix memory leaks
+* [2421](https://github.com/AcademySoftwareFoundation/openexr/pull/2421)
+Add test to validate python module reading of unfinished parts
+* [2416](https://github.com/AcademySoftwareFoundation/openexr/pull/2416)
+Add HTJ2K256 and HTJ2K32 to data compression scheme list
+* [2414](https://github.com/AcademySoftwareFoundation/openexr/pull/2414)
+Check for invalid Slice base and type in `TileProcess::copy_sample_count`
+* [2413](https://github.com/AcademySoftwareFoundation/openexr/pull/2413)
+Python: reject dataWindow size mismatch before write
+* [2409](https://github.com/AcademySoftwareFoundation/openexr/pull/2409)
+Python: fix docstring indentation
+* [2407](https://github.com/AcademySoftwareFoundation/openexr/pull/2407)
+Support NumPy float vectors in Python headers
+* [2406](https://github.com/AcademySoftwareFoundation/openexr/pull/2406)
+Update urllib3 to 2.7 in website/requirements.txt
+* [2405](https://github.com/AcademySoftwareFoundation/openexr/pull/2405)
+Signal HTJ2K* compressors as lossless
+* [2403](https://github.com/AcademySoftwareFoundation/openexr/pull/2403)
+htj2k compressor: validate codestream dimensions
+* [2402](https://github.com/AcademySoftwareFoundation/openexr/pull/2402)
+Release notes, news, and CVE info for v3.4.11, 3.3.11, 3.2.9
+* [2398](https://github.com/AcademySoftwareFoundation/openexr/pull/2398)
+Arcmantis/warning deprecate WidenFilename
+* [2397](https://github.com/AcademySoftwareFoundation/openexr/pull/2397)
+Cast to `size_t` to avoid int overflow in `ht_undo_impl`
+* [2392](https://github.com/AcademySoftwareFoundation/openexr/pull/2392)
+Correct index rejection for string access.
+* [2388](https://github.com/AcademySoftwareFoundation/openexr/pull/2388)
+Relax NaN comparison in compression tests
+* [2387](https://github.com/AcademySoftwareFoundation/openexr/pull/2387)
+Add install instructions for python module
+* [2385](https://github.com/AcademySoftwareFoundation/openexr/pull/2385)
+cmake: require YMM asm for inline AVX probe
+* [2373](https://github.com/AcademySoftwareFoundation/openexr/pull/2373)
+Bump and pin website/requirements.txt
+* [2359](https://github.com/AcademySoftwareFoundation/openexr/pull/2359)
+Add reference to "contribute" and Clotributor in README.md and website
+* [2148](https://github.com/AcademySoftwareFoundation/openexr/pull/2148)
+Fix unfinished multi-part file loading in the python wrapper
+
+### Merged Workflow Pull Requests
+
+* [2399](https://github.com/AcademySoftwareFoundation/openexr/pull/2399)
+Bump github/codeql-action from 4.35.2 to 4.35.4
+* [2396](https://github.com/AcademySoftwareFoundation/openexr/pull/2396)
+Bump sphinx from 7.2.6 to 7.4.7 in /website
+* [2394](https://github.com/AcademySoftwareFoundation/openexr/pull/2394)
+Bump platforms from 1.0.0 to 1.1.0
+
+## Version 3.4.11 (April 29, 2026)
+
+Patch release that addresses the following security vulnerabilities:
+
+* [CVE-2026-42217](https://www.cve.org/CVERecord?id=CVE-2026-42217)
+Shift exponent overflow in `readVariableLengthInteger()` (`ImfIDManifest.cpp`)
+* [CVE-2026-42216](https://www.cve.org/CVERecord?id=CVE-2026-42216)
+Out-of-bounds read in `IDManifest::init()` during prefix expansion
+* [CVE-2026-41142](https://www.cve.org/CVERecord?id=CVE-2026-41142)
+Integer overflow in `ImageChannel::resize` leads to heap OOB write via OpenEXRUtil public API
+
+Also:
+
+* OSS-fuzz [504280155](https://issues.oss-fuzz.com/issues/504280155)
+Heap-buffer-overflow in `DwaCompressor_uncompress`
+* OSS-fuzz [505062709](https://issues.oss-fuzz.com/issues/505062709)
+Null-dereference READ in `Imf_3_3::prefixFromLayerName`
+
+Build fixes:
+
+- Fix Windows ARM64EC build issues and correct SIMD ARM NEON path for ARM64/EC
+
+Also, some minor documentation updates:
+
+- GitHub Security Advisories are the preferred way of reporting
+  vulnerabilities, not email.
+- Some clarification around handling of UFT-8 of file paths
+
+### Merged Pull Requests
+
+* [2383](https://github.com/AcademySoftwareFoundation/openexr/pull/2383)
+validate that the uncompressed sizes recorded in the dwa header are valid
+* [2382](https://github.com/AcademySoftwareFoundation/openexr/pull/2382)
+Fix Null-dereference READ in prefixFromLayerName
+* [2378](https://github.com/AcademySoftwareFoundation/openexr/pull/2378)
+Harden IDManifest parsing against illegal shift and string prefix OOB
+* [2377](https://github.com/AcademySoftwareFoundation/openexr/pull/2377)
+Fix OOB read when expanding IDManifest prefix-compressed strings
+* [2375](https://github.com/AcademySoftwareFoundation/openexr/pull/2375)
+Minor changes to website index page to make some sentences clearer. A…
+* [2368](https://github.com/AcademySoftwareFoundation/openexr/pull/2368)
+Add release notes and news for v3.4.10, v3.3.10, v3.2.8
+* [2367](https://github.com/AcademySoftwareFoundation/openexr/pull/2367)
+Fix int overflow in ImageChannel::resize pixel count
+* [2364](https://github.com/AcademySoftwareFoundation/openexr/pull/2364)
+Recommend GH Security Advisories for vulnerability reporting
+* [2361](https://github.com/AcademySoftwareFoundation/openexr/pull/2361)
+Add documentation and test for UTF-8 file paths
+* [2344](https://github.com/AcademySoftwareFoundation/openexr/pull/2344)
+Fix Windows ARM64EC build issues and correct SIMD ARM NEON path for ARM64/EC
+
+### Merged Workflow Pull Requests
+
+* [2370](https://github.com/AcademySoftwareFoundation/openexr/pull/2370)
+Bump msys2/setup-msys2 from 2.31.0 to 2.31.1
+* [2366](https://github.com/AcademySoftwareFoundation/openexr/pull/2366)
+Add workflow dispatch trigger to release-sign.yml
+* [2363](https://github.com/AcademySoftwareFoundation/openexr/pull/2363)
+Bump vmactions/freebsd-vm from 1.4.4 to 1.4.5
+* [2362](https://github.com/AcademySoftwareFoundation/openexr/pull/2362)
+Bump github/codeql-action from 4.35.1 to 4.35.2
+
+## Version 3.4.10 (April 17, 2026)
+
+Patch release that addresses the following security vulnerabilities:
+
+* [CVE-2026-39886](https://www.cve.org/CVERecord?id=CVE-2026-39886) HTJ2K Signed Integer Overflow in `ht_undo_impl()`
+* [CVE-2026-40244](https://www.cve.org/CVERecord?id=CVE-2026-40244) Integer overflow in DWA `setupChannelData` `planarUncRle` pointer arithmetic (missed variant of CVE-2026-34589)
+* [CVE-2026-40250](https://www.cve.org/CVERecord?id=CVE-2026-40250) Integer overflow in DWA decoder `outBufferEnd` pointer arithmetic (missed variant of CVE-2026-34589)
+
+### Merged Pull Requests
+
+* [2346](https://github.com/AcademySoftwareFoundation/openexr/pull/2346)
+Fix integer overflow in internal_dwa_compressor.h
+* [2345](https://github.com/AcademySoftwareFoundation/openexr/pull/2345)
+Fix HTJ2K bytes-per-line integer overflow in internal_ht.cpp
+* [2340](https://github.com/AcademySoftwareFoundation/openexr/pull/2340)
+Fix 3.4.9 cve list formatting
+* [2339](https://github.com/AcademySoftwareFoundation/openexr/pull/2339)
+fix link formatting typo
+* [2337](https://github.com/AcademySoftwareFoundation/openexr/pull/2337)
+notes and news for v3.4.9, v3.3.9, v3.2.7
+* [2334](https://github.com/AcademySoftwareFoundation/openexr/pull/2334)
+Add CVE-2026-34589,34588,34545,34544,34543,34380,34379,34378 to SECURITY.md
+* [2316](https://github.com/AcademySoftwareFoundation/openexr/pull/2316)
+Fix Pinned-Dependencies Scorecard alert in website workflow
+
+### Merged Workflow Pull Requests
+
+* [2360](https://github.com/AcademySoftwareFoundation/openexr/pull/2360)
+Bump actions/cache from 5.0.4 to 5.0.5
+* [2354](https://github.com/AcademySoftwareFoundation/openexr/pull/2354)
+Bump actions/upload-artifact from 7.0.0 to 7.0.1
+* [2343](https://github.com/AcademySoftwareFoundation/openexr/pull/2343)
+Bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0
+* [2341](https://github.com/AcademySoftwareFoundation/openexr/pull/2341)
+Bump jmertic/slack-release-notifier from 32206e01ee0b0f66865d2be13bb3c62e474b5ce0 to 9d7d3a84563d2ebc8f7b2271be6c9568fedd7f3a
+* [2338](https://github.com/AcademySoftwareFoundation/openexr/pull/2338)
+Fix CodeQL SARIF upload ref for pull_request workflows
+* [2336](https://github.com/AcademySoftwareFoundation/openexr/pull/2336)
+Bump pypa/cibuildwheel from 3.4.0 to 3.4.1
+* [2333](https://github.com/AcademySoftwareFoundation/openexr/pull/2333)
+Add CI test to validate "cmake --install .. --prefix <path>
+
+## Version 3.4.9 (April 3, 2026)
+
+Patch release that addresses several security vulnerabilities.
+
+This release also fixes a build issue where the library symlinks would
+get installed in the incorrect location when overriding the cached
+install prefix path.
+
+This release addresses the following CVEs:
+* [CVE-2026-34589](https://www.cve.org/CVERecord?id=CVE-2026-34589) DWA Lossy Decoder Heap Out-of-Bounds Write
+* [CVE-2026-34588](https://www.cve.org/CVERecord?id=CVE-2026-34588) Signed 32-bit Overflow in PIZ Decoder Leads to OOB Read/Write
+* [CVE-2026-34380](https://www.cve.org/CVERecord?id=CVE-2026-34380) Signed integer overflow (undefined behavior) in undo_pxr24_impl may allow bounds-check bypass in PXR24 decompression
+* [CVE-2026-34379](https://www.cve.org/CVERecord?id=CVE-2026-34379) Misaligned write in LossyDctDecoder_execute leading to undefined behavior (DWA/DWAB decompression)
+* [CVE-2026-34378](https://www.cve.org/CVERecord?id=CVE-2026-34378) Signed integer overflow in generic_unpack() when parsing EXR files with crafted negative dataWindow.min.x
+
+### Merged Pull Requests
+
+* [2329](https://github.com/AcademySoftwareFoundation/openexr/pull/2329)
+Fix signed integer overflow in `LossyDctDecoder_execute()` pointer arithmatic
+* [2328](https://github.com/AcademySoftwareFoundation/openexr/pull/2328)
+fix integer overflow in PIZ wavelet buffer arithmetic
+* [2327](https://github.com/AcademySoftwareFoundation/openexr/pull/2327)
+Add a message about image size limits and OOM errors to SECURITY.md and website
+* [2326](https://github.com/AcademySoftwareFoundation/openexr/pull/2326)
+Fix shared lib symlink installation path
+* [2324](https://github.com/AcademySoftwareFoundation/openexr/pull/2324)
+Fix misaligned memory access in `LossyDctDecoder_execute` HALF→FLOAT expansion
+* [2323](https://github.com/AcademySoftwareFoundation/openexr/pull/2323)
+fix signed integer overflow in `undo_pxr24_impl()`
+* [2321](https://github.com/AcademySoftwareFoundation/openexr/pull/2321)
+Fix integer overflow in `srcbuffer` pointer arithmetic in `unpack_*`
+* [2317](https://github.com/AcademySoftwareFoundation/openexr/pull/2317)
+Add "cherry" and "changes" options to release.py
+
+### Merged Workflow Pull Requests
+
+* [2332](https://github.com/AcademySoftwareFoundation/openexr/pull/2332)
+Bump vmactions/freebsd-vm from 1.4.3 to 1.4.4
+* [2331](https://github.com/AcademySoftwareFoundation/openexr/pull/2331)
+Bump github/codeql-action from 4.34.1 to 4.35.1
+* [2330](https://github.com/AcademySoftwareFoundation/openexr/pull/2330)
+Bump jmertic/slack-release-notifier from f824585541879ef2a6388268eccb9be210aa6878 to 32206e01ee0b0f66865d2be13bb3c62e474b5ce0
+* [2325](https://github.com/AcademySoftwareFoundation/openexr/pull/2325)
+Bump sigstore/gh-action-sigstore-python from 3.2.0 to 3.3.0
+* [2320](https://github.com/AcademySoftwareFoundation/openexr/pull/2320)
+Bump jmertic/slack-release-notifier from 35fad060af5559c24decdec0f701e6ba93566704 to f824585541879ef2a6388268eccb9be210aa6878
+* [2319](https://github.com/AcademySoftwareFoundation/openexr/pull/2319)
+Bump msys2/setup-msys2 from 2.30.0 to 2.31.0
+* [2318](https://github.com/AcademySoftwareFoundation/openexr/pull/2318)
+Bump github/codeql-action from 4.33.0 to 4.34.1
+
+## Version 3.4.8 (March 26, 2026)
+
+Patch release with several bug/build fixes:
+
+- Fix an integer-overflow bug reading malformed files compressed with
+  B44A/B44B 
+- Fix a buffer-overrun bug reading malformed files compressed with PXR24
+- Fix a bug compressing half data with ZIPS/ZIP data when the
+  compressed size equals packed size
+- Single part files no longer get assigned a part name when writing
+  via the python module
+- Fix a build failure on FreeBSD involving `threads.h`
+
+This also eliminates several compiler warnings, particularly about the
+deprecated `isOptimizationEnabled()` API and deprecates standard
+attributes. 
+
+This version addresses the following security vulnerabilities:
+
+* [CVE-2026-34544](https://www.cve.org/CVERecord?id=CVE-2026-34544) integer overflow to OOB write in uncompress_b44_impl()
+* [CVE-2026-34543](https://www.cve.org/CVERecord?id=CVE-2026-34543) Heap information disclosure in PXR24 decompression via unchecked decompressed size (undo_pxr24_impl)
+
+### Merged Pull Requests
+
+* [2312](https://github.com/AcademySoftwareFoundation/openexr/pull/2312)
+Fix B44/B44A integer overflow: use uint64_t for row offset
+* [2310](https://github.com/AcademySoftwareFoundation/openexr/pull/2310)
+PXR24: reject zlib output that does not match packed payload size
+* [2307](https://github.com/AcademySoftwareFoundation/openexr/pull/2307)
+Fix ZIPS/ZIP encoder corruption when compressed size equals packed size
+* [2303](https://github.com/AcademySoftwareFoundation/openexr/pull/2303)
+Fix MinGW -Walloc-size-larger-than= in general_attr string test
+* [2301](https://github.com/AcademySoftwareFoundation/openexr/pull/2301)
+don't insert name attributes into single part files
+* [2300](https://github.com/AcademySoftwareFoundation/openexr/pull/2300)
+Fix FreeBSD build failure involving threads.h, and add FreeBSD CI build
+* [2124](https://github.com/AcademySoftwareFoundation/openexr/pull/2124)
+Suppress deprecation warnings for isOptimizationEnabled and standard attributes
+
+### Merged Workflow Pull Requests
+
+* [2315](https://github.com/AcademySoftwareFoundation/openexr/pull/2315)
+Force macos cibuildwheel to use Xcode clang
+* [2311](https://github.com/AcademySoftwareFoundation/openexr/pull/2311)
+Bump actions/cache from 5.0.3 to 5.0.4
+* [2304](https://github.com/AcademySoftwareFoundation/openexr/pull/2304)
+Bump github/codeql-action from 4.32.6 to 4.33.0
+* [2297](https://github.com/AcademySoftwareFoundation/openexr/pull/2297)
+Restore Analysis build
+* [2295](https://github.com/AcademySoftwareFoundation/openexr/pull/2295)
+Restrict workflow permissions for ci_steps, release-sign, and codeql
+* [2294](https://github.com/AcademySoftwareFoundation/openexr/pull/2294)
+Pin pypa/cibuildwheel actions to release sha
+
+## Version 3.4.7 (March 15, 2026)
+
+Patch release bug/build fixes:
+
+* Fix an integer overflow decoding very wide htj2k images
+* Fix build failure with glibc 2.43
+* Fix Windows symbol visibility warnings
+
+Full changelog: [v3.4.6..v3.4.7](https://github.com/AcademySoftwareFoundation/openexr/compare/v3.4.6..v3.4.7)
+
+This version addresses the following security vulnerabilities:
+
+* [CVE-2026-34545](https://www.cve.org/CVERecord?id=CVE-2026-34545)
+  integer overflow lead to OOB in HTJ2K decoder
+
+### Merged Pull Requests
+
+* [2291](https://github.com/AcademySoftwareFoundation/openexr/pull/2291)
+Fix integer overflow in htj2k decode with width > 32767
+
+* [2283](https://github.com/AcademySoftwareFoundation/openexr/pull/2283)
+update SECURITY with CVE info for PR #2256
+
+* [2282](https://github.com/AcademySoftwareFoundation/openexr/pull/2282)
+Remove `website_preview_link` workflow
+
+* [2281](https://github.com/AcademySoftwareFoundation/openexr/pull/2281)
+Disable visibility attributes on Windows to fix msys2 -Wattributes warning
+
+* [2262](https://github.com/AcademySoftwareFoundation/openexr/pull/2262)
+Fix build failure with glibc 2.43 due to C11 threads.h conflicts
+
+### Merged Workflow Pull Requests
+
+* [2292](https://github.com/AcademySoftwareFoundation/openexr/pull/2292)
+Bump actions/download-artifact from 8.0.0 to 8.0.1
+
+* [2289](https://github.com/AcademySoftwareFoundation/openexr/pull/2289)
+Bump scikit-build-core from 0.12.1 to 0.12.2
+
+* [2288](https://github.com/AcademySoftwareFoundation/openexr/pull/2288)
+Bump jmertic/slack-release-notifier from 6fa159048d5313ff1177d248ad84beb627571670 to 35fad060af5559c24decdec0f701e6ba93566704
+
+* [2287](https://github.com/AcademySoftwareFoundation/openexr/pull/2287)
+Bump pypa/cibuildwheel from 3.3 to 3.4
+
 ## Version 3.4.6 (March 1, 2026)
 
 Patch release with several bug fixes, enhancements, and build improvements.
@@ -97,14 +831,12 @@
   enforced, which prevents an integer overflow when using the
   `CompositeDeepScanLine` API to combine multiple deep parts.
 
-* :bug: `IlmThread` now builds properl with glibc 2.43.
-
 * :wrench: In `IlmThreadPool`, replace deprecated `std::atomic_load /
   std::atomic_exchange` overloads for `std::shared_ptr` with the C++20
   `std::atomic<std::shared_ptr<T>>` interface when available.
 
 * :bug: The ``ZIP`` and ``ZIPS`` Compressor objects had incorrect
-  compression types set, although the ill effects were miminal as the
+  compression types set, although the ill effects were minimal as the
   value is seldom used.
   
 * :bug: Enable SSE2 on 32-bit x86 builds to fix test failures
@@ -146,101 +878,105 @@
 
 Also, this release bumps the vendered version of `libdeflate` to 1.25.
 
+This release addresses the following CVEs:
+* [CVE-2026-27622](https://www.cve.org/CVERecord?id=CVE-2026-27622)
+CompositeDeepScanLine integer-overflow leads to heap OOB write
+
 ### Merged Pull Requests:
 
-* [2274](https://github.com/AcademySoftwareFoundation/openexr/pulls/2274)
+* [2274](https://github.com/AcademySoftwareFoundation/openexr/pull/2274)
 Update the vendored libdeflate to 1.25
-* [2273](https://github.com/AcademySoftwareFoundation/openexr/pulls/2273)
+* [2273](https://github.com/AcademySoftwareFoundation/openexr/pull/2273)
 Release Python GIL during I/O operations in bindings
-* [2272](https://github.com/AcademySoftwareFoundation/openexr/pulls/2272)
+* [2272](https://github.com/AcademySoftwareFoundation/openexr/pull/2272)
 Fix build failure with -march=sandybridge (#2231)
-* [2271](https://github.com/AcademySoftwareFoundation/openexr/pulls/2271)
+* [2271](https://github.com/AcademySoftwareFoundation/openexr/pull/2271)
 Enable SSE2 on 32-bit x86 builds to fix test failures
-* [2269](https://github.com/AcademySoftwareFoundation/openexr/pulls/2269)
+* [2269](https://github.com/AcademySoftwareFoundation/openexr/pull/2269)
 Mention OpenEXR dependencies on README
-* [2268](https://github.com/AcademySoftwareFoundation/openexr/pulls/2268)
+* [2268](https://github.com/AcademySoftwareFoundation/openexr/pull/2268)
 Bazel: Use openjph 0.26.3
-* [2267](https://github.com/AcademySoftwareFoundation/openexr/pulls/2267)
+* [2267](https://github.com/AcademySoftwareFoundation/openexr/pull/2267)
 Bump rules_cc from 0.2.16 to 0.2.17
-* [2265](https://github.com/AcademySoftwareFoundation/openexr/pulls/2265)
+* [2265](https://github.com/AcademySoftwareFoundation/openexr/pull/2265)
 Bump jmertic/slack-release-notifier
-* [2264](https://github.com/AcademySoftwareFoundation/openexr/pulls/2264)
+* [2264](https://github.com/AcademySoftwareFoundation/openexr/pull/2264)
 Bump github/codeql-action from 4.32.3 to 4.32.4
-* [2263](https://github.com/AcademySoftwareFoundation/openexr/pulls/2263)
+* [2263](https://github.com/AcademySoftwareFoundation/openexr/pull/2263)
 Add sanity check for correct arguments to exrstdattr
-* [2261](https://github.com/AcademySoftwareFoundation/openexr/pulls/2261)
+* [2261](https://github.com/AcademySoftwareFoundation/openexr/pull/2261)
 Fix sentence fragment typo in website python docs
-* [2260](https://github.com/AcademySoftwareFoundation/openexr/pulls/2260)
+* [2260](https://github.com/AcademySoftwareFoundation/openexr/pull/2260)
 Add reference to `OPENEXR_FORCE_EMBEDDED_CORE` on install page
-* [2259](https://github.com/AcademySoftwareFoundation/openexr/pulls/2259)
+* [2259](https://github.com/AcademySoftwareFoundation/openexr/pull/2259)
 Fix ZipCompressor to initialize compresson type correctly
-* [2258](https://github.com/AcademySoftwareFoundation/openexr/pulls/2258)
+* [2258](https://github.com/AcademySoftwareFoundation/openexr/pull/2258)
 Bazel: Remove WORKSPACE.bazel file
-* [2256](https://github.com/AcademySoftwareFoundation/openexr/pulls/2256)
+* [2256](https://github.com/AcademySoftwareFoundation/openexr/pull/2256)
 Report an error if a deep pixel as more than `UINT_MAX` samples
-* [2253](https://github.com/AcademySoftwareFoundation/openexr/pulls/2253)
+* [2253](https://github.com/AcademySoftwareFoundation/openexr/pull/2253)
 Add CVE-2025-64181 to SECURITY.md
-* [2252](https://github.com/AcademySoftwareFoundation/openexr/pulls/2252)
+* [2252](https://github.com/AcademySoftwareFoundation/openexr/pull/2252)
 Add news and release notes for v3.4.5 and v3.3.7
-* [2250](https://github.com/AcademySoftwareFoundation/openexr/pulls/2250)
+* [2250](https://github.com/AcademySoftwareFoundation/openexr/pull/2250)
 Vendor OpenJPH v0.26.3
-* [2249](https://github.com/AcademySoftwareFoundation/openexr/pulls/2249)
+* [2249](https://github.com/AcademySoftwareFoundation/openexr/pull/2249)
 Add python support for Opaque Attributes
-* [2248](https://github.com/AcademySoftwareFoundation/openexr/pulls/2248)
+* [2248](https://github.com/AcademySoftwareFoundation/openexr/pull/2248)
 Fix tag check, git rm, and sed calls in vendor_openjph.sh
-* [2247](https://github.com/AcademySoftwareFoundation/openexr/pulls/2247)
+* [2247](https://github.com/AcademySoftwareFoundation/openexr/pull/2247)
 Bump github/codeql-action from 4.31.9 to 4.32.3
-* [2243](https://github.com/AcademySoftwareFoundation/openexr/pulls/2243)
+* [2243](https://github.com/AcademySoftwareFoundation/openexr/pull/2243)
 IlmThread: fix deprecated atomic `shared_ptr` ops for `shared_ptr` (GCC 15)
-* [2240](https://github.com/AcademySoftwareFoundation/openexr/pulls/2240)
+* [2240](https://github.com/AcademySoftwareFoundation/openexr/pull/2240)
 Bump actions/cache from 5.0.1 to 5.0.3
-* [2239](https://github.com/AcademySoftwareFoundation/openexr/pulls/2239)
+* [2239](https://github.com/AcademySoftwareFoundation/openexr/pull/2239)
 Fix links and formatting in CONTRIBUTING.md
-* [2236](https://github.com/AcademySoftwareFoundation/openexr/pulls/2236)
+* [2236](https://github.com/AcademySoftwareFoundation/openexr/pull/2236)
 Bump actions/checkout from 6.0.1 to 6.0.2
-* [2235](https://github.com/AcademySoftwareFoundation/openexr/pulls/2235)
+* [2235](https://github.com/AcademySoftwareFoundation/openexr/pull/2235)
 Bump actions/setup-python from 6.1.0 to 6.2.0
-* [2227](https://github.com/AcademySoftwareFoundation/openexr/pulls/2227)
+* [2227](https://github.com/AcademySoftwareFoundation/openexr/pull/2227)
 Bump rules_cc from 0.2.14 to 0.2.16
-* [2226](https://github.com/AcademySoftwareFoundation/openexr/pulls/2226)
+* [2226](https://github.com/AcademySoftwareFoundation/openexr/pull/2226)
 Bump bazel_skylib from 1.8.2 to 1.9.0
-* [2225](https://github.com/AcademySoftwareFoundation/openexr/pulls/2225)
+* [2225](https://github.com/AcademySoftwareFoundation/openexr/pull/2225)
 Rename external/OpenJPH/src/core/common to openjph, instead of using a symlink
-* [2224](https://github.com/AcademySoftwareFoundation/openexr/pulls/2224)
+* [2224](https://github.com/AcademySoftwareFoundation/openexr/pull/2224)
 Bump github/codeql-action from 4.31.8 to 4.31.9
-* [2222](https://github.com/AcademySoftwareFoundation/openexr/pulls/2222)
+* [2222](https://github.com/AcademySoftwareFoundation/openexr/pull/2222)
 Bump actions/upload-artifact from 5 to 6
-* [2221](https://github.com/AcademySoftwareFoundation/openexr/pulls/2221)
+* [2221](https://github.com/AcademySoftwareFoundation/openexr/pull/2221)
 Bump actions/cache from 4.3.0 to 5.0.1
-* [2220](https://github.com/AcademySoftwareFoundation/openexr/pulls/2220)
+* [2220](https://github.com/AcademySoftwareFoundation/openexr/pull/2220)
 Bump msys2/setup-msys2 from 2.29.0 to 2.30.0
-* [2219](https://github.com/AcademySoftwareFoundation/openexr/pulls/2219)
+* [2219](https://github.com/AcademySoftwareFoundation/openexr/pull/2219)
 Bump actions/download-artifact from 6.0.0 to 7.0.0
-* [2218](https://github.com/AcademySoftwareFoundation/openexr/pulls/2218)
+* [2218](https://github.com/AcademySoftwareFoundation/openexr/pull/2218)
 Bump github/codeql-action from 4.31.7 to 4.31.8
-* [2216](https://github.com/AcademySoftwareFoundation/openexr/pulls/2216)
+* [2216](https://github.com/AcademySoftwareFoundation/openexr/pull/2216)
 Add option to compile c++ libraries with an embedded core library
-* [2215](https://github.com/AcademySoftwareFoundation/openexr/pulls/2215)
+* [2215](https://github.com/AcademySoftwareFoundation/openexr/pull/2215)
 Bump imath from 3.2.2 to 3.2.2.bcr.1
-* [2214](https://github.com/AcademySoftwareFoundation/openexr/pulls/2214)
+* [2214](https://github.com/AcademySoftwareFoundation/openexr/pull/2214)
 Bump github/codeql-action from 4.31.5 to 4.31.7
-* [2213](https://github.com/AcademySoftwareFoundation/openexr/pulls/2213)
+* [2213](https://github.com/AcademySoftwareFoundation/openexr/pull/2213)
 Bump sigstore/gh-action-sigstore-python from 3.1.0 to 3.2.0
-* [2212](https://github.com/AcademySoftwareFoundation/openexr/pulls/2212)
+* [2212](https://github.com/AcademySoftwareFoundation/openexr/pull/2212)
 Bump actions/checkout from 6.0.0 to 6.0.1
-* [2210](https://github.com/AcademySoftwareFoundation/openexr/pulls/2210)
+* [2210](https://github.com/AcademySoftwareFoundation/openexr/pull/2210)
 Bump actions/setup-python from 6.0.0 to 6.1.0
-* [2209](https://github.com/AcademySoftwareFoundation/openexr/pulls/2209)
+* [2209](https://github.com/AcademySoftwareFoundation/openexr/pull/2209)
 Bump github/codeql-action from 4.31.4 to 4.31.5
-* [2208](https://github.com/AcademySoftwareFoundation/openexr/pulls/2208)
+* [2208](https://github.com/AcademySoftwareFoundation/openexr/pull/2208)
 Remove enable-beta-ecosystem since Bazel is no officially supported
-* [2204](https://github.com/AcademySoftwareFoundation/openexr/pulls/2204)
+* [2204](https://github.com/AcademySoftwareFoundation/openexr/pull/2204)
 Bump actions/checkout from 3.6.0 to 6.0.0
-* [2202](https://github.com/AcademySoftwareFoundation/openexr/pulls/2202)
+* [2202](https://github.com/AcademySoftwareFoundation/openexr/pull/2202)
 Bump github/codeql-action from 4.31.3 to 4.31.4
-* [2193](https://github.com/AcademySoftwareFoundation/openexr/pulls/2193)
+* [2193](https://github.com/AcademySoftwareFoundation/openexr/pull/2193)
 Vendor in an internal version of OpenJPH
-* [2167](https://github.com/AcademySoftwareFoundation/openexr/pulls/2167)
+* [2167](https://github.com/AcademySoftwareFoundation/openexr/pull/2167)
 Bazel build: Bump openjph to 0.25.0 and libdeflate to 1.25
 
 ## Version 3.4.5 (February 21, 2026)
@@ -249,9 +985,13 @@
 `istream_nonparallel_read` that could lead to a buffer overflow on
 invalid input data.
 
+This release addresses the following CVEs:
+* [CVE-2026-26981](https://www.cve.org/CVERecord?id=CVE-2026-26981)
+Heap-buffer-overflow via signed integer underflow in ImfContextInit.cpp
+
 ### Merged Pull Requests:
 
-* [2244](https://github.com/AcademySoftwareFoundation/pulls/2244)
+* [2244](https://github.com/AcademySoftwareFoundation/openexr/pull/2244)
 Fix incorrect size check in `istream_nonparallel_read`
 
 ## Version 3.4.4 (November 19, 2025)
@@ -272,53 +1012,53 @@
 
 ### Merged Pull Requests:
 
-* [2199](https://github.com/AcademySoftwareFoundation/pulls/2199)
+* [2199](https://github.com/AcademySoftwareFoundation/openexr/pull/2199)
 OpenEXRCore: fix `ILMTHREAD_THREADING_ENABLED` checks
-* [2198](https://github.com/AcademySoftwareFoundation/pulls/2198)
+* [2198](https://github.com/AcademySoftwareFoundation/openexr/pull/2198)
 OpenEXRCore: avoid direct dependency on imath
-* [2196](https://github.com/AcademySoftwareFoundation/pulls/2196)
+* [2196](https://github.com/AcademySoftwareFoundation/openexr/pull/2196)
 Bump github/codeql-action from 4.31.2 to 4.31.3
-* [2194](https://github.com/AcademySoftwareFoundation/pulls/2194)
+* [2194](https://github.com/AcademySoftwareFoundation/openexr/pull/2194)
 Add missing copywrite comments
-* [2192](https://github.com/AcademySoftwareFoundation/pulls/2192)
+* [2192](https://github.com/AcademySoftwareFoundation/openexr/pull/2192)
 Bump pypa/cibuildwheel from 3.2 to 3.3
-* [2190](https://github.com/AcademySoftwareFoundation/pulls/2190)
+* [2190](https://github.com/AcademySoftwareFoundation/openexr/pull/2190)
 Bump github/codeql-action from 3.27.9 to 4.31.2
-* [2189](https://github.com/AcademySoftwareFoundation/pulls/2189)
+* [2189](https://github.com/AcademySoftwareFoundation/openexr/pull/2189)
 Bump msys2/setup-msys2 from 2.27.0 to 2.29.0
-* [2188](https://github.com/AcademySoftwareFoundation/pulls/2188)
+* [2188](https://github.com/AcademySoftwareFoundation/openexr/pull/2188)
 Bump actions/download-artifact from 4.1.8 to 6.0.0
-* [2187](https://github.com/AcademySoftwareFoundation/pulls/2187)
+* [2187](https://github.com/AcademySoftwareFoundation/openexr/pull/2187)
 Bump ossf/scorecard-action from 2.4.0 to 2.4.3
-* [2186](https://github.com/AcademySoftwareFoundation/pulls/2186)
+* [2186](https://github.com/AcademySoftwareFoundation/openexr/pull/2186)
 Bump pypa/cibuildwheel from 2.23 to 3.2
-* [2185](https://github.com/AcademySoftwareFoundation/pulls/2185)
+* [2185](https://github.com/AcademySoftwareFoundation/openexr/pull/2185)
 Bump actions/cache from 4.2.0 to 4.3.0
-* [2184](https://github.com/AcademySoftwareFoundation/pulls/2184)
+* [2184](https://github.com/AcademySoftwareFoundation/openexr/pull/2184)
 Bump actions/upload-artifact from 4 to 5
-* [2183](https://github.com/AcademySoftwareFoundation/pulls/2183)
+* [2183](https://github.com/AcademySoftwareFoundation/openexr/pull/2183)
 Bump actions/checkout from 2.7.0 to 5.0.0
-* [2182](https://github.com/AcademySoftwareFoundation/pulls/2182)
+* [2182](https://github.com/AcademySoftwareFoundation/openexr/pull/2182)
 Add recent CVEs to SECURITY.md
-* [2181](https://github.com/AcademySoftwareFoundation/pulls/2181)
+* [2181](https://github.com/AcademySoftwareFoundation/openexr/pull/2181)
 Bump sigstore/gh-action-sigstore-python from 3.0.0 to 3.1.0
-* [2180](https://github.com/AcademySoftwareFoundation/pulls/2180)
+* [2180](https://github.com/AcademySoftwareFoundation/openexr/pull/2180)
 Bump actions/setup-python from 5.3.0 to 6.0.0
-* [2178](https://github.com/AcademySoftwareFoundation/pulls/2178)
+* [2178](https://github.com/AcademySoftwareFoundation/openexr/pull/2178)
 Bump snyk/actions from 0.4.0 to 1.0.0
-* [2177](https://github.com/AcademySoftwareFoundation/pulls/2177)
+* [2177](https://github.com/AcademySoftwareFoundation/openexr/pull/2177)
 Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.13.0
-* [2176](https://github.com/AcademySoftwareFoundation/pulls/2176)
+* [2176](https://github.com/AcademySoftwareFoundation/openexr/pull/2176)
 Imporve Bazel support
-* [2175](https://github.com/AcademySoftwareFoundation/pulls/2175)
+* [2175](https://github.com/AcademySoftwareFoundation/openexr/pull/2175)
 Converts asserts in internal_ht.cpp to if statement that throws an exception.
-* [2174](https://github.com/AcademySoftwareFoundation/pulls/2174)
+* [2174](https://github.com/AcademySoftwareFoundation/openexr/pull/2174)
 DWA: initialize linear/nonlinear tables at runtime
-* [2173](https://github.com/AcademySoftwareFoundation/pulls/2173)
+* [2173](https://github.com/AcademySoftwareFoundation/openexr/pull/2173)
 Update macos-13 to macos-15-intel
-* [2171](https://github.com/AcademySoftwareFoundation/pulls/2171)
+* [2171](https://github.com/AcademySoftwareFoundation/openexr/pull/2171)
 Properly set `OpenEXR_error` in legacy python API
-* [2126](https://github.com/AcademySoftwareFoundation/pulls/2126)
+* [2126](https://github.com/AcademySoftwareFoundation/openexr/pull/2126)
 B44: initialize exp/log tables at runtime
 
 
@@ -329,15 +1069,29 @@
 
 Specifically:
 
-* Buffer overflow in PyOpenEXR_old's `channels()` and `channel()` in
+- Buffer overflow in PyOpenEXR_old's `channels()` and `channel()` in
   legacy python, reported by Joshua Rogers (GitHub: MegaManSec).
-* Use after free in PyObject_StealAttrString in legacy python, reported
+- Use after free in PyObject_StealAttrString in legacy python, reported
   by Joshua Rogers (GitHub: MegaManSec).
-* Use of Uninitialized Memory in openexr, reported by Aldo Ristori
+- Use of Uninitialized Memory in openexr, reported by Aldo Ristori
   (GitHub: Kaldreic).
-* Heap-based Buffer Overflow Remote Code Execution Vulnerability,
+- Heap-based Buffer Overflow Remote Code Execution Vulnerability,
   reported by Trend Micro Zero Day Initiative.
 
+This release addresses the following CVEs:
+* [CVE-2025-64183](https://www.cve.org/CVERecord?id=CVE-2025-64183)
+Use after free in PyObject_StealAttrString
+* [CVE-2025-64182](https://www.cve.org/CVERecord?id=CVE-2025-64182)
+Buffer overflow in PyOpenEXR_old's channels() and channel()
+* [CVE-2025-64181](https://www.cve.org/CVERecord?id=CVE-2025-64181)
+Security Issue: Use of Uninitialized Memory in openexr
+* [CVE-2025-12840](https://www.cve.org/CVERecord?id=CVE-2025-12840)
+Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability
+* [CVE-2025-12839](https://www.cve.org/CVERecord?id=CVE-2025-12839)
+Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability
+* [CVE-2025-12495](https://www.cve.org/CVERecord?id=CVE-2025-12495)
+Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability
+
 Also:
 
 * OSS-fuzz [456158449](https://issues.oss-fuzz.com/issues/456158449)
@@ -360,18 +1114,12 @@
   which makes it possible to set `CMAKE_DEBUG_POSTFIX=""`.
 
 This version also bumps the auto-fetched version of OpenJPH to
-0.24.5. OpenJPH 0.24.5 addresses these OSS-Fuzz issues:
+0.24.4. OpenJPH 0.24.4 addresses these OSS-Fuzz issues:
 		
-* OSS-fuzz [456837230](https://issues.oss-fuzz.com/issues/456837230)
-Crash in `ojph::local::param_cod::~param_cod`
-* OSS-fuzz [456248580](https://issues.oss-fuzz.com/issues/456248580)
-Null-dereference READ in `ojph::local::param_cod::~param_cod`
 * OSS-fuzz [455374208](https://issues.oss-fuzz.com/issues/455374208)
 Floating-point-exception in `ojph::local::tile::pre_alloc`
 * OSS-fuzz [444963190](https://issues.oss-fuzz.com/issues/444963190)
 Index-out-of-bounds in `ojph::local::param_qcd::read_qcc`
-* OSS-fuzz [444889300](https://issues.oss-fuzz.com/issues/444889300)
-Heap-buffer-overflow in `ojph::mem_infile::read`
 * OSS-fuzz [444878558](https://issues.oss-fuzz.com/issues/444878558)
 Segv on unknown address in `ojph::local::param_qcd::~param_qcd`
 * OSS-fuzz [444878557](https://issues.oss-fuzz.com/issues/444878557)
@@ -405,10 +1153,6 @@
 
 Patch release that fixes a Windows build issue introduced in v3.4.1.
 
-This version also bumps the auto-fetched Imath version to v3.2.2,
-which resolves a build problem with newer versions of cmake, involving
-duplicate library aliases.
-
 No change in functionality.
 
 ### Merged Pull Requests:
@@ -657,6 +1401,160 @@
 * [1852](https://github.com/AcademySoftwareFoundation/openexr/pull/1852)
 Add an option to use TBB as the global provider
 
+## Version 3.3.11 (April 29, 2026)
+
+Patch release for 3.3 that addresses the following security
+vulnerabilities:
+
+* [CVE-2026-42217](https://www.cve.org/CVERecord?id=CVE-2026-42217)
+Shift exponent overflow in `readVariableLengthInteger()` (`ImfIDManifest.cpp`)
+* [CVE-2026-42216](https://www.cve.org/CVERecord?id=CVE-2026-42216)
+Out-of-bounds read in `IDManifest::init()` during prefix expansion
+* [CVE-2026-41142](https://www.cve.org/CVERecord?id=CVE-2026-41142)
+Integer overflow in `ImageChannel::resize` leads to heap OOB write via OpenEXRUtil public API
+
+Also:
+
+* OSS-fuzz [504280155](https://issues.oss-fuzz.com/issues/504280155)
+Heap-buffer-overflow in `DwaCompressor_uncompress`
+
+### Merged Pull Requests
+
+* [2383](https://github.com/AcademySoftwareFoundation/openexr/pull/2383)
+validate that the uncompressed sizes recorded in the dwa header are valid
+* [2378](https://github.com/AcademySoftwareFoundation/openexr/pull/2378)
+Harden IDManifest parsing against illegal shift and string prefix OOB
+* [2377](https://github.com/AcademySoftwareFoundation/openexr/pull/2377)
+Fix OOB read when expanding IDManifest prefix-compressed strings
+* [2367](https://github.com/AcademySoftwareFoundation/openexr/pull/2367)
+Fix int overflow in ImageChannel::resize pixel count
+
+## Version 3.3.10 (April 17, 2026)
+
+Patch release that addresses the following security vulnerabilities:
+
+* [CVE-2026-40244](https://www.cve.org/CVERecord?id=CVE-2026-40244) Integer overflow in DWA setupChannelData planarUncRle pointer arithmetic (missed variant of CVE-2026-34589)
+* [CVE-2026-40250](https://www.cve.org/CVERecord?id=CVE-2026-40250) Integer overflow in DWA decoder outBufferEnd pointer arithmetic (missed variant of CVE-2026-34589)
+
+### Merged Pull Requests
+
+* [2346](https://github.com/AcademySoftwareFoundation/openexr/pull/2346)
+Fix integer overflow in internal_dwa_compressor.h
+
+## Version 3.3.9 (April 4, 2026)
+
+Patch release for v3.3 that addresses the following security vulnerabilities:
+
+* [CVE-2026-34589](https://www.cve.org/CVERecord?id=CVE-2026-34589) DWA Lossy Decoder Heap Out-of-Bounds Write
+* [CVE-2026-34588](https://www.cve.org/CVERecord?id=CVE-2026-34588) Signed 32-bit Overflow in PIZ Decoder Leads to OOB Read/Write
+* [CVE-2026-34544](https://www.cve.org/CVERecord?id=CVE-2026-34544) integer overflow to OOB write in uncompress_b44_impl()
+* [CVE-2026-34543](https://www.cve.org/CVERecord?id=CVE-2026-34543) Heap information disclosure in PXR24 decompression via unchecked decompressed size (undo_pxr24_impl)
+* [CVE-2026-34380](https://www.cve.org/CVERecord?id=CVE-2026-34380) Signed integer overflow (undefined behavior) in undo_pxr24_impl may allow bounds-check bypass in PXR24 decompression
+* [CVE-2026-34379](https://www.cve.org/CVERecord?id=CVE-2026-34379) Misaligned write in LossyDctDecoder_execute leading to undefined behavior (DWA/DWAB decompression)
+* [CVE-2026-34378](https://www.cve.org/CVERecord?id=CVE-2026-34378) Signed integer overflow in generic_unpack() when parsing EXR files with crafted negative dataWindow.min.x
+
+### Merged Pull Requests
+
+* [2329](https://github.com/AcademySoftwareFoundation/openexr/pull/2329)
+Fix signed integer overflow in `LossyDctDecoder_execute()` pointer arithmatic
+* [2328](https://github.com/AcademySoftwareFoundation/openexr/pull/2328)
+fix integer overflow in PIZ wavelet buffer arithmetic
+* [2324](https://github.com/AcademySoftwareFoundation/openexr/pull/2324)
+Fix misaligned memory access in `LossyDctDecoder_execute` HALF→FLOAT expansion
+* [2323](https://github.com/AcademySoftwareFoundation/openexr/pull/2323)
+fix signed integer overflow in `undo_pxr24_impl()`
+* [2321](https://github.com/AcademySoftwareFoundation/openexr/pull/2321)
+Fix integer overflow in `srcbuffer` pointer arithmetic in `unpack_*`
+* [2312](https://github.com/AcademySoftwareFoundation/openexr/pull/2312)
+Fix B44/B44A integer overflow: use uint64_t for row offset
+* [2310](https://github.com/AcademySoftwareFoundation/openexr/pull/2310)
+PXR24: reject zlib output that does not match packed payload size
+
+## Version 3.3.8 (March 1, 2026)
+
+Patch release that prevents an integer overflow when using the
+`CompositeDeepScanLine` API to combine multiple deep parts.
+
+This release addresses the following CVEs:
+* [CVE-2026-27622](https://www.cve.org/CVERecord?id=CVE-2026-27622)
+CompositeDeepScanLine integer-overflow leads to heap OOB write
+
+### Merged Pull Requests:
+
+* [2256](https://github.com/AcademySoftwareFoundation/openexr/pull/2256)
+Report an error if a deep pixel as more than UINT_MAX samples
+
+## Version 3.3.7 (February 19, 2026)
+
+Patch release that fixes an incorrect size check in
+`istream_nonparallel_read` that could lead to a buffer overflow on
+invalid input data.
+
+This release addresses the following CVEs:
+* [CVE-2026-26981](https://www.cve.org/CVERecord?id=CVE-2026-26981)
+Heap-buffer-overflow via signed integer underflow in ImfContextInit.cpp
+
+
+### Merged Pull Requests:
+
+* [2244](https://github.com/AcademySoftwareFoundation/openexr/pull/2244)
+Fix incorrect size check in `istream_nonparallel_read`
+
+## Version 3.3.6 (November 4, 2025)
+
+Patch release that addresses several bugs, primarily involving
+properly rejecting corrupt input data.
+
+Specifically:
+
+* Buffer overflow in PyOpenEXR_old's `channels()` and `channel()` in
+  legacy python, reported by Joshua Rogers (GitHub: MegaManSec).
+* Use after free in PyObject_StealAttrString in legacy python, reported
+  by Joshua Rogers (GitHub: MegaManSec).
+* Use of Uninitialized Memory in openexr, reported by Aldo Ristori
+  (GitHub: Kaldreic).
+* Heap-based Buffer Overflow Remote Code Execution Vulnerability,
+  reported by Trend Micro Zero Day Initiative.
+
+Other fixes:
+* Only populate `CMAKE_DEBUG_POSTFIX` with `_d` if it is undefined,
+  which makes it possible to set `CMAKE_DEBUG_POSTFIX=""`.
+
+This release addresses the following CVEs:
+* [CVE-2025-64183](https://www.cve.org/CVERecord?id=CVE-2025-64183)
+Use after free in PyObject_StealAttrString
+* [CVE-2025-64182](https://www.cve.org/CVERecord?id=CVE-2025-64182)
+Buffer overflow in PyOpenEXR_old's channels() and channel()
+* [CVE-2025-64181](https://www.cve.org/CVERecord?id=CVE-2025-64181)
+Security Issue: Use of Uninitialized Memory in openexr
+* [CVE-2025-12840](https://www.cve.org/CVERecord?id=CVE-2025-12840)
+Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability
+* [CVE-2025-12839](https://www.cve.org/CVERecord?id=CVE-2025-12839)
+Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability
+* [CVE-2025-12495](https://www.cve.org/CVERecord?id=CVE-2025-12495)
+Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability
+
+### Merged Pull Requests:
+
+* [2168](https://github.com/AcademySoftwareFoundation/openexr/pull/2168)
+Fix improper use of `Py_DECREF` in legacy python module
+* [2166](https://github.com/AcademySoftwareFoundation/openexr/pull/2166)
+Only define CMAKE_DEBUG_POSTFIX if it is not already defined
+* [2164](https://github.com/AcademySoftwareFoundation/openexr/pull/2164)
+check storage_mode when computing chunk sizes
+* [2163](https://github.com/AcademySoftwareFoundation/openexr/pull/2163)
+Check for image size overflow in legacy python module
+* [2162](https://github.com/AcademySoftwareFoundation/openexr/pull/2162)
+verify packed/unpacked size with uncompressed data
+* [2161](https://github.com/AcademySoftwareFoundation/openexr/pull/2161)
+ImfCheckFile: handle partial deep tiles
+* [2160](https://github.com/AcademySoftwareFoundation/openexr/pull/2160)
+Fix issues with negative coordinates and sampling != 0
+* [2159](https://github.com/AcademySoftwareFoundation/openexr/pull/2159)
+Fix memset in exr_read_chunk when nread is negative
+* [2156](https://github.com/AcademySoftwareFoundation/openexr/pull/2156)
+Fix handling of corrupt RLE data
+
 ## Version 3.3.5 (July 26, 2025)
 
 Patch release with a couple bug/performance fixes:
@@ -1228,6 +2126,107 @@
 Propagate dwa core 3 1
 * [1418](https://github.com/AcademySoftwareFoundation/openexr/pull/1418)
 
+## Version 3.2.9 (April 29, 2026)
+
+Patch release for 3.2 that addresses the following security
+vulnerabilities:
+
+* [CVE-2026-42217](https://www.cve.org/CVERecord?id=CVE-2026-42217)
+Shift exponent overflow in `readVariableLengthInteger()` (`ImfIDManifest.cpp`)
+* [CVE-2026-42216](https://www.cve.org/CVERecord?id=CVE-2026-42216)
+Out-of-bounds read in `IDManifest::init()` during prefix expansion
+* [CVE-2026-41142](https://www.cve.org/CVERecord?id=CVE-2026-41142)
+Integer overflow in `ImageChannel::resize` leads to heap OOB write via OpenEXRUtil public API
+
+Also:
+
+* OSS-fuzz [504280155](https://issues.oss-fuzz.com/issues/504280155)
+Heap-buffer-overflow in `DwaCompressor_uncompress`
+
+### Merged Pull Requests
+
+* [2383](https://github.com/AcademySoftwareFoundation/openexr/pull/2383)
+validate that the uncompressed sizes recorded in the dwa header are valid
+* [2378](https://github.com/AcademySoftwareFoundation/openexr/pull/2378)
+Harden IDManifest parsing against illegal shift and string prefix OOB
+* [2377](https://github.com/AcademySoftwareFoundation/openexr/pull/2377)
+Fix OOB read when expanding IDManifest prefix-compressed strings
+* [2367](https://github.com/AcademySoftwareFoundation/openexr/pull/2367)
+Fix int overflow in ImageChannel::resize pixel count
+
+## Version 3.2.8 (April 17, 2026)
+
+Patch release that addresses the following security vulnerabilities:
+
+* [CVE-2026-40244](https://www.cve.org/CVERecord?id=CVE-2026-40244) Integer overflow in DWA setupChannelData planarUncRle pointer arithmetic (missed variant of CVE-2026-34589)
+* [CVE-2026-40250](https://www.cve.org/CVERecord?id=CVE-2026-40250) Integer overflow in DWA decoder outBufferEnd pointer arithmetic (missed variant of CVE-2026-34589)
+
+### Merged Pull Requests
+
+* [2346](https://github.com/AcademySoftwareFoundation/openexr/pull/2346)
+Fix integer overflow in internal_dwa_compressor.h
+
+## Version 3.2.7 (April 3, 2026)
+
+Patch release for v3.2 that addresses the following security vulnerabilities:
+
+* [CVE-2026-34589](https://www.cve.org/CVERecord?id=CVE-2026-34589) DWA Lossy Decoder Heap Out-of-Bounds Write
+* [CVE-2026-34588](https://www.cve.org/CVERecord?id=CVE-2026-34588) Signed 32-bit Overflow in PIZ Decoder Leads to OOB Read/Write
+* [CVE-2026-34544](https://www.cve.org/CVERecord?id=CVE-2026-34544) integer overflow to OOB write in uncompress_b44_impl()
+* [CVE-2026-34543](https://www.cve.org/CVERecord?id=CVE-2026-34543) Heap information disclosure in PXR24 decompression via unchecked decompressed size (undo_pxr24_impl)
+* [CVE-2026-34380](https://www.cve.org/CVERecord?id=CVE-2026-34380) Signed integer overflow (undefined behavior) in undo_pxr24_impl may allow bounds-check bypass in PXR24 decompression
+* [CVE-2026-34379](https://www.cve.org/CVERecord?id=CVE-2026-34379) Misaligned write in LossyDctDecoder_execute leading to undefined behavior (DWA/DWAB decompression)
+
+### Merged Pull Requests
+
+* [2329](https://github.com/AcademySoftwareFoundation/openexr/pull/2329)
+Fix signed integer overflow in `LossyDctDecoder_execute()` pointer arithmatic
+* [2328](https://github.com/AcademySoftwareFoundation/openexr/pull/2328)
+fix integer overflow in PIZ wavelet buffer arithmetic
+* [2324](https://github.com/AcademySoftwareFoundation/openexr/pull/2324)
+Fix misaligned memory access in `LossyDctDecoder_execute` HALF→FLOAT expansion
+* [2323](https://github.com/AcademySoftwareFoundation/openexr/pull/2323)
+fix signed integer overflow in `undo_pxr24_impl()`
+* [2312](https://github.com/AcademySoftwareFoundation/openexr/pull/2312)
+Fix B44/B44A integer overflow: use uint64_t for row offset
+* [2310](https://github.com/AcademySoftwareFoundation/openexr/pull/2310)
+PXR24: reject zlib output that does not match packed payload size
+
+## Version 3.2.6 (March 1, 2026)
+
+Patch release that prevents an integer overflow when using the
+`CompositeDeepScanLine` API to combine multiple deep parts.
+
+This release addresses the following CVEs:
+* [CVE-2026-27622](https://www.cve.org/CVERecord?id=CVE-2026-27622)
+CompositeDeepScanLine integer-overflow leads to heap OOB write
+
+### Merged Pull Requests:
+
+* [2256](https://github.com/AcademySoftwareFoundation/openexr/pull/2256)
+
+## Version 3.2.5 (November 4, 2025)
+
+Patch release that addresses bugs in the python module's legacy API.
+
+- Buffer overflow in PyOpenEXR_old's `channels()` and `channel()` in
+  legacy python, reported by Joshua Rogers (GitHub: MegaManSec).
+- Use after free in PyObject_StealAttrString in legacy python, reported
+  by Joshua Rogers (GitHub: MegaManSec).
+
+This release addresses the following CVEs:
+* [CVE-2025-64182](https://www.cve.org/CVERecord?id=CVE-2025-64182)
+Buffer overflow in PyOpenEXR_old's channels() and channel()
+* [CVE-2025-64183](https://www.cve.org/CVERecord?id=CVE-2025-64183)
+Use after free in PyObject_StealAttrString
+
+### Merged Pull Requests:
+
+* [2168](https://github.com/AcademySoftwareFoundation/openexr/pull/2168)
+ Fix improper use of `Py_DECREF` in legacy python module
+* [2163](https://github.com/AcademySoftwareFoundation/openexr/pull/2163)
+Check for image size overflow in legacy python module
+
 ## Version 3.2.4 (March 26, 2024)
 
 Patch release that fixes handling of dwa compression in OpenEXRCore library.
@@ -6097,7 +7096,7 @@
 
 * Added new function, isImfMagic (). (Florian Kainz)
 	
-## Version 1.0.6:
+## Version 1.0.6
 
 * Added README.win32 to disted files.
 
@@ -6223,7 +7222,7 @@
 * OpenEXR is now covered by a modified BSD license.  See LICENSE
 	  for the new terms.
 
-## Version 1.0.3:
+## Version 1.0.3
 
 ### Detailed Changes:
 
diff -Nru openexr-3.4.6+ds/cmake/CMakeLists.txt openexr-3.4.14/cmake/CMakeLists.txt
--- openexr-3.4.6+ds/cmake/CMakeLists.txt	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/cmake/CMakeLists.txt	2026-08-05 04:29:55.000000000 +0300
@@ -37,7 +37,17 @@
     int edx = 0;
     __asm__(
         \"xgetbv     ;\"
-        \"vzeroupper  \"
+        //
+        // This feature probe is specifically used to decide whether we can
+        // compile the DWA fast path that contains GCC-style inline asm using
+        // AVX/YMM instructions. Some toolchains (notably older Apple 'as'
+        // invoked by GCC on legacy macOS) can assemble xgetbv/vzeroupper but
+        // still reject YMM/VEX instructions with errors like:
+        //   no such instruction: `vmovaps (%rsi), %ymm0'
+        // So include a representative YMM instruction in the try-compile.
+        //
+        \"vmovaps    %%ymm0, %%ymm0 ;\"
+        \"vzeroupper                \"
         : \"=a\"(eax), \"=d\"(edx) : \"c\"(n) : );
 #else
 #    error No SSE support enabled to query AVX support
diff -Nru openexr-3.4.6+ds/cmake/LibraryDefine.cmake openexr-3.4.14/cmake/LibraryDefine.cmake
--- openexr-3.4.6+ds/cmake/LibraryDefine.cmake	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/cmake/LibraryDefine.cmake	2026-08-05 04:29:55.000000000 +0300
@@ -96,8 +96,8 @@
     set(verlibname ${CMAKE_SHARED_LIBRARY_PREFIX}${libname}${OPENEXR_LIB_SUFFIX}${CMAKE_${uppercase_CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX})
     set(baselibname ${CMAKE_SHARED_LIBRARY_PREFIX}${libname}${CMAKE_${uppercase_CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX})
     file(CREATE_LINK ${verlibname} ${CMAKE_CURRENT_BINARY_DIR}/${baselibname} SYMBOLIC)
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${baselibname} DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})
-    install(CODE "message(STATUS \"Creating symlink ${CMAKE_INSTALL_FULL_LIBDIR}/${baselibname} -> ${verlibname}\")")
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${baselibname} DESTINATION ${CMAKE_INSTALL_LIBDIR})
+    install(CODE "message(STATUS \"Creating symlink ${CMAKE_INSTALL_LIBDIR}/${baselibname} -> ${verlibname}\")")
     set(verlibname)
     set(baselibname)
   endif()
diff -Nru openexr-3.4.6+ds/cmake/OpenEXRConfig.cmake.in openexr-3.4.14/cmake/OpenEXRConfig.cmake.in
--- openexr-3.4.6+ds/cmake/OpenEXRConfig.cmake.in	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/cmake/OpenEXRConfig.cmake.in	2026-08-05 04:29:55.000000000 +0300
@@ -18,7 +18,7 @@
   find_dependency(libdeflate)
 endif()
 
-if (@openjph_FOUND@)
+if (NOT @OPENEXR_USE_INTERNAL_OPENJPH@)
   find_dependency(openjph)
 endif()
 
diff -Nru openexr-3.4.6+ds/cmake/OpenEXRConfig.h.in openexr-3.4.14/cmake/OpenEXRConfig.h.in
--- openexr-3.4.6+ds/cmake/OpenEXRConfig.h.in	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/cmake/OpenEXRConfig.h.in	2026-08-05 04:29:55.000000000 +0300
@@ -127,8 +127,10 @@
 ///
 /// @{
 
+// Visibility attributes are ELF-specific; on Windows (MSVC or MinGW) they are
+// not supported and trigger -Wattributes warnings, so use empty macros there.
 #if defined(OPENEXR_ENABLE_API_VISIBILITY) &&                                  \
-    !(defined(OPENEXR_DLL) || defined(_MSC_VER))
+    !(defined(OPENEXR_DLL) || defined(_MSC_VER) || defined(_WIN32))
 #    define OPENEXR_PUBLIC_SYMBOL_ATTRIBUTE                                    \
         __attribute__ ((__visibility__ ("default")))
 #    define OPENEXR_PRIVATE_SYMBOL_ATTRIBUTE                                   \
diff -Nru openexr-3.4.6+ds/cmake/OpenEXRSetup.cmake openexr-3.4.14/cmake/OpenEXRSetup.cmake
--- openexr-3.4.6+ds/cmake/OpenEXRSetup.cmake	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/cmake/OpenEXRSetup.cmake	2026-08-05 04:29:55.000000000 +0300
@@ -257,6 +257,9 @@
 
 option(OPENEXR_FORCE_INTERNAL_OPENJPH "Force downloading OpenJPH from a git repo" OFF)
 
+set(OPENEXR_USE_INTERNAL_OPENJPH 0 CACHE INTERNAL
+    "Whether OpenEXR bundles vendored OpenJPH into OpenEXRCore (0 or 1)")
+
 if (NOT OPENEXR_FORCE_INTERNAL_OPENJPH)
   find_package(openjph CONFIG QUIET)
   if(openjph_FOUND)
@@ -286,6 +289,8 @@
   set(EXR_OPENJPH_PKGCONFIG_REQUIRES "openjph >= 0.21.0")
 else()
   # Using internal openjph
+  set(OPENEXR_USE_INTERNAL_OPENJPH 1 CACHE INTERNAL
+      "Whether OpenEXR bundles vendored OpenJPH into OpenEXRCore (0 or 1)" FORCE)
 
   # extract the openjph version variables from ojph_version.h
   set(openjph_SOURCE_DIR "${CMAKE_SOURCE_DIR}/external/openjph")
diff -Nru openexr-3.4.6+ds/CONTRIBUTING.md openexr-3.4.14/CONTRIBUTING.md
--- openexr-3.4.6+ds/CONTRIBUTING.md	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/CONTRIBUTING.md	2026-08-05 04:29:55.000000000 +0300
@@ -508,6 +508,7 @@
 will validate the build. Click on the ``Details`` link to
 preview. Also, a link to this preview will be added automatically to
 the PR description.
+
 ### Test Images
 
 To contribute a new test image, commit it to the
@@ -527,309 +528,249 @@
 
 ## Creating a Patch Release
 
-Making a patch release typically involves merging changes from the
-main branch into the release branch, since all development generally
-takes place on the main branch. The usual workflow is to group these
-changes together and merge them all just prior to a release, rather
-than merging one-by-one as the changes go into main, although merging
-along the way is acceptable as well. For OpenEXR and Imath, a patch
-release typically involves under a dozen commits, so it's not a huge
-amount of work to organize them all at once.
+These instructions are for project administrators who have "push"
+access on the GitHub repo.
+
+Making a patch release involves merging changes from the main branch
+into the release branch, since all development takes place on the main
+branch. The process involves these steps:
+
+1. Cherry-pick commits from main to the RB- release branch
+2. Bump the version number
+3. Add release notes to CHANGES.md
+4. Create a "release candidate" tag and announce it publicly
+5. Draft the GitHub release
+6. Create a signed release tag
+7. Publish the release
+8. Add an entry to the website news page
+
+Helper scripts in ``share/util/release/`` automate many of the steps
+in the process.
 
 A patch release *must* be ABI-compatible with preceding minor releases
 and should be validated with an ABI-checker tool such as
 [``abipkgdiff``](https://manpages.ubuntu.com/manpages/lunar/en/man1/abipkgdiff.1.html).
 
-These instructions are for project administrators who have "push"
-access on the GitHub repo.
+### Labeling PRs for Release
 
-The preferred workflow is:
+Commits for the patch release are identified via labels on PRs. Create
+a label for the release, beginning with a `v`, as in
+``v3.4.11``. Assign this label to all PRs to be included in the
+release.
 
-1. Make a PR that merges appropriate changes from the main branch to
-   the release branch:
+If a PR should be merged into more than one release branch, i.e. a fix
+goes into v3.4 and v3.3, simply add multiple labels, one per release.
 
-   a. In a forked clone, create a branch to hold the patch commits,
-      e.g. ``v3.1.9-fixes``.
+It's good practice to label _every_ PR to identify what
+release it goes into, even if it's the next minor/major release. That
+helps make sure PRs don't get overlooked.
 
-   b. Cherry-pick the appropriate commits from ``main``, resolving any
-      merge conflicts.
-
-   c. Increment ``OPENEXR_VERSION_PATCH`` in
-      [src/lib/OpenEXRCore/openexr_version.h](src/lib/OpenEXRCore/openexr_version.h)
-
-   d. Update the ``IMATH_TAG`` setting in
-      [cmake/OpenEXRSetup.cmake](cmake/OpenEXRSetup.cmake) to
-      correspond to the proper Imath release.
-
-   e. Add release notes to [CHANGES.md](CHANGES.md):
-
-      - Generate a list of links to merged pull requests.
-
-        Use ``git log`` to identify the merged commits, and for each
-        commit, and add a link in the notes to the corresponding PR
-        that merged it to ``main``. Citing PR's in the release notes
-        is preferable to citing raw commits because the PR's often
-        have helpful information and discussion missing from the
-        commit descriptions, and the commit history is readily
-        accessible via ``git log`` anyway.
-
-        The typical OpenEXR project workflow uses "squash and merge"
-        to merge PR's into ``main``, so the changes involved in each
-        PR end up on ``main`` as a single commit. This is preferable
-        because a raw PR often includes numerous commits that address
-        comments and feedback or fix typos or mistakes, intermediate
-        steps not helpful to the preserved history of the main
-        development thread. Note that GitHub's "squash and merge"
-        helpfully appends the PR number to the commit subject line.
-
-        Note that when this PR is merged to the release branch, it
-        should go in via "rebase and merge" that the release branch
-        retains the granular changes, described below.
-
-      - Generate a list of OSS-Fuzz issues addressed.
-
-        These are security concerns, so they deserve special
-        attention. Provide a link in the notes to the issue at
-        https://bugs.chromium.org/p/oss-fuzz, including the issue id
-        number and description.
-
-      - If there are any public CVE's, mention them explicitly with a
-        link to the CVE registry item.
-
-      - Provide an executive summary of the patch changes, in a few
-        sentences as well as bullet points if appropriate.
-
-      - Choose a proposed release date at least several days in
-        advance.
-
-   f. If there are any public CVE's, reference them in
-      [SECURITY.md](SECURITY.md).
-
-   g. Submit the PR for others to review. The PR should go *to the
-      release branch, not ``main``*, obviously.
-
-   h. After others have had a chance to sanity-check the changes,
-      merge the PR *with "rebase and merge"*.  Unlike with the usual
-      PR's merged to main, it is essential to retain the individual
-      commits on the release branch. That way, the release branch
-      commit history retains the details of the changes.
-
-   i. If further fixes come in that need to go into the release, push
-      them to the PR branch. It's not absolutely essential that all
-      changes to the release branch go in via a PR. The PR is simply a
-      convenient forum for publicly discussing and reviewing the
-      composition of the release.
-
-2. Tag the release with a ``-rc`` "release candidate" tag,
-   e.g. ``v3.1.9-rc``.
-
-3. Validate ABI compatibility. Build at the release candidate tag and
-   run
-   [abipkgdiff](https://manpages.ubuntu.com/manpages/lunar/en/man1/abipkgdiff.1.html)
-   against a build of the previous patch release to confirm that no
-   ABI changes have leaked in. Additions to the ABI are acceptable for
-   a patch release, but there should be no symbol changes and no
-   symbols removed. If there are, back up and fix them before proceeding.
-
-4. Send mail to ``openexr-dev@lists.aswf.io`` announcing the staging
-   of the release with link to the release candidate tag. Include the
-   release notes from [CHANGES.md](CHANGES.md) for review.
+The `share/util/release/log.py` script prints all PRs in the order
+they were merged, annotated by their release labels.
 
-5. Draft the release on the GitHub
-   [Releases](https://github.com/AcademySoftwareFoundation/openexr/releases)
-   page.  Include the summary from the notes in
-   [CHANGES.md](CHANGES.md), but don't include the list of PR's.
+### Security Issues
 
-   Create the release from the latest ``--rc`` tag, and give it a name
-   that begins with ``v``, i.e. ``v3.1.9``.
+When a PR addresses a security vulnerability, mention it in the
+description or comments:
 
-   Save the release as a "draft".
+    Addresses CVE-2026-39886
 
-6. Wait at least 48 hours, to give the community time to discover and
-   report any obvious problems. Avoid the temptation to rush changes
-   into a release and publish it immediately, as that is uncomfortably
-   error prone.
+The release scripts will detect this and add an appropriate entry to the
+release notes.
 
-   If additional fixes need to go in before release:
+When a PR addresses an OSS-Fuzz issue, mention it by url in the
+description or comments:
 
-   a. Merge commits to the release branch. Push them directly, no need
-      for a pull request.
+    Addresses https://issues.oss-fuzz.com/issues/456158449
 
-   b. Update the release notes in a separate commit.
+The release scripts will detect this and add an appropriate entry to the
+release notes.
 
-   c. Re-tag with a incremented "release candidate" number,
-      e.g. ``v3.1.9-rc2``.  
+### GPG
 
-   d. Send an email update to ``openexr-dev@lists.aswf.io`` notifying
-      the community of the addition and the new tag.
+The signed git tags associated with the release require a [GPG
+key](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
+that is
+[registered](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
+with your GitHub account and git config.
 
-7. Create a signed release tag
+## Release Workflow
 
-   a. Make sure you have a [GPG
-      key](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
-      and it is
-      [registered](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
-      with your GitHub account and git config.
+Once all PRs have appropriate release labels, follow these steps, all
+while on the appropriate `RB-` branch:
 
-   b. Create a signed tag with the release name via `git tag -s v3.1.9`.
+1. Cherry-pick commits from the main branch to the release branch.
 
-   c. Push the tag via `git push --tags`
+   This prints the SHAs for the commits associated with the
+   labeled PRs. The output will look something like:
+      
+        % python share/util/release/cherry.py v3.4.8
+        git cherry-pick 2e32c6b 3df0122 6155271
+        changes.py v3.4.8 2311 2294 2315
+          
+   Copy/paste the `git cherry-pick` command to the shell.
 
-8. Publish the release
+   If there are conflicts, resolve them and continue.
 
-   a. Click the "Publish release" button on the GitHub release draft
+   If the conflicts are too messy to resolve, consider
+   cherry-picking the commits one at a time. The `cherry.py`
+   script accepts a `--single` option that prints the commits one
+   `git cherry-pick` command per line:
 
-   b. Send an email to ``openexr-dev@lists.aswf.io`` officially
-      announcing the release.
-
-9. Update the ``release`` branch, which should always point to the
-   most recent patch of the most recent minor release, i.e. the most
-   preferred release.
+        % python share/util/release/cherry.py --single v3.4.8
+        git cherry-pick 2e32c6b # Bump actions/cache from 5.0.3 to 5.0.4 (#2311)
+        git cherry-pick 3df0122 # Pin pypa/cibuildwheel actions to release sha (#2294)
+        git cherry-pick 6155271 # Force macos cibuildwheel to use Xcode clang (#2315)
+        changes.py v3.4.8 2311 2294 2315
 
-   From a clone of the main repo:
+2. Bump the OpenEXR version.
 
-       % git checkout release
-       % git merge RB-3.1
-       % git push
-         
-10. Submit a PR that adds the release notes to [CHANGES.md](CHANGES.md)
-    on the main branch. Cherry-pick the release notes commit from
-    the release branch.
-
-    - If any changes have gone into [SECURITY.md](SECURITY), cherry-pick
-      the associated commit as well.
-
-    - Also include in this PR edits to [``website/news.rst``](website/news.rst)
-      that add an announcement of the release.
-
-11. After review/merge of the updates to ``website/news.rst``, build the
-    website at https://readthedocs.org/projects/openexr.
-
-12. If the release has resolved any OSS-Fuzz issues, update the
-    associated pages at https://bugs.chromium.org/p/oss-fuzz with a
-    reference to the release.
-
-13. If the release has resolved any public CVE's, request an update
-    from the registry service providing the release and a link to the
-    release notes.
+   Edit the number in `src/lib/OpenEXRCore/openexr_version.h`. Commit
+   and push the change.
 
-## Creating a Major/Minor Release
+3. Create an entry in the release notes file `CHANGES.md`.
 
-A major/minor release is created from the main branch, assuming there
-are no changes on ``main`` that should *not* go into the release. We
-don't generally allow experimental changes onto ``main``. Anything
-accepted onto ``main`` should be intended for the next release.
+   The `changes.py` script takes a list of PRs
+   and adds entries in the `CHANGES.md` file:
+        
+        % python share/util/release/changes.py v3.4.8 2311 2294 2315
 
-The overall workflow is similar to a patch release, as described
-above, but it's simpler because there is no cherry-picking and merging
-of commits. The major/minor release is simply a snapshot of ``main``.
+   This adds the `Merged Pull Requests` and the
+   `Merged Workflow Requests` sections. It also adds a `Security` section
+   that lists the CVEs and OSS-Fuzz issues addressed. 
+   
+   Edit the section by hand to add a summary.
 
-To create a new release from the ``main`` branch:
+4. Create a `-rc` "release candidate" tag.
 
-1. Confirm that the ``OPENEXR_VERSION_MAJOR``,
-   ``OPENEXR_VERSION_MINOR``, and ``OPENEXR_VERSION_PATCH`` value in
-   [src/lib/OpenEXRCore/openexr_version.h](src/lib/OpenEXRCore/openexr_version.h)
-   are correct. The OpenEXR project policy is that the values on the
-   main branch, which is the bleeding edge of development, correspond
-   to the next minor release, with the patch set to 0.
+   Obviously, build locally and confirm nothing broke in the
+   cherry-picking commits and resolving conflicts.
+   
+   Push the release branch and confirm the CI succeeds before creating
+   the release candidate tag.
 
-2. Update the release notes in [CHANGES.md](CHANGES.md):
+   Create the tag with a `-rc` suffix, e.g. `v3.4.12-rc`.
+   
+   The `tag.py` script create a signed tag using the release notes as
+   the tag message:
 
-   - Write a high-level summary of the features and improvements.
+        % python share/util/release/tag.py v3.4.12-rc
 
-   - Include the log of all PR's that have *not* been merged into the
-     previous minor release.
+   Adding the release notes as the tag message is good practice.
 
-   - Mention any OSS-Fuzz issues. Provide a link in the notes to the issue at
-     https://bugs.chromium.org/p/oss-fuzz, including the issue id
-     number and description.
+   Push the tags. This triggers the `python-wheels-publish-test`
+   CI workflow. Confirm it succeeds.
 
-   - If there are any public CVE's, mention them explicitly with a
-     link to the CVE registry item.
+   Run the `candidate.py` script to format a
+   message about the release candidate, formatted in HTML. Pipe the
+   output to a `.html` file:
+   
+        % python share/util/release/candidate.py v3.4.12-rc > v3.4.12.html
 
-   - Submit this change as a separate PR.
+   Load the file in a browers to render the formatting, then
+   copy/paste the contents into an email to `openexr-dev@lists.aswf.io`
+   and the `#openexr` Slack channel.
 
-3. Add a mention of the release to [``website/news.rst``](website/news.rst)
+   If any problems arise after tagging the candidate, fix them in
+   subsequent commits and create an
+   additional tag with a number appended:
+   
+        % python share/util/release/tag.py v3.4.12-rc2
 
-   - Submit this change as a separate PR.
+5. Draft the GitHub release
 
-4. Increment the ``OPENEXR_LIB_SOVERSION`` setting in [CMakeLists.txt](CMakeLists.txt).
+        % python share/util/release/draft.py v3.4.12
+       
+   Verify the notes look correct on the
+   [Releases](https://github.com/AcademySoftwareFoundation/openexr/releases)
+   page and edit as appropriate. Save as a "draft". DO NOT PUBLISH THE
+   RELEASE YET.
 
-   - The SO version increases whenever, and only when, the ABI changes
-     in non-backwards-compatible ways. Consistent with the semantic
-     versioning policy, this usually happens at major and minor
-     releases, but never on a patch release.
+6. Create a signed release tag
 
-   - Submit this change as a separate PR for review.
+   Assuming you have your PGP key set up, create the release tag at
+   the same commit as the release candidate:
+   
+        % python share/util/release/tag.py v3.4.12
+
+7. Publish the release
+
+   Set the GitHub release to correspond to the release tag (the `Tag:
+   Select tag` option), then click the "Publish release" button on the
+   GitHub release draft.
+
+   Monitor the GitHub actions on the
+   `https://github.com/AcademySoftwareFoundation/openexr` repo to
+   ensure everything builds properly. 
+   
+   Update the `release` branch, which should always point to the
+   most recent patch of the most recent minor release, i.e. the most
+   preferred release.
 
-5. Once the above PR's are merged, create the release branch with the
-   ``RB`` prefix, e.g. ``RB-3.2``.
+   From a clone of the main repo:
 
-6. Update the ``IMATH_TAG`` setting in
-   [cmake/OpenEXRSetup.cmake](cmake/OpenEXRSetup.cmake) to correspond
-   to the proper Imath release.
+        % git checkout release
+        % git merge RB-3.1
+        % git push
 
-7. Tag the release with a ``-rc`` "release candidate" tag,
-   e.g. ``v3.2.0-rc``.
+   Publishing the release triggers a post on the `#openexr` Slack
+   channel, but you must manually send an email to
+   `openexr-dev@lists.aswf.io` officially announcing the release.
 
-8. Send mail to ``openexr-dev@lists.aswf.io`` announcing the staging
-   of the release with link to the release candidate tag. Include the
-   release notes from [CHANGES.md](CHANGES.md) for review.
+8. Add an entry to the website news page.
 
-9. If additional fixes need to go in before release:
+        % python share/util/release/news.py v3.4.12
 
-   a. Merge commits to the release branch. Push them directly, no need
-      for a pull request.
+   This pulls the release notes from `CHANGES.md` and inserts a news
+   item in `website/news.rst` and `website/latest_news_title.rst`.
 
-   b. Update the release notes in a separate commit.
+   Commit this change, and then cherry-pick the commit from the
+   release branch containing the notes and submit the pair as a
+   PR. This pulls the release notes for the patch into the main branch.
 
-   c. Re-tag with a incremented "release candidate" number,
-      e.g. ``v3.2.0-rc2``.  
+## Creating a Major/Minor Release
 
-   d. Send a email update to ``openexr-dev@lists.aswf.io`` notifying
-      the community of the addition.
+A major/minor release is created from the main branch, assuming there
+are no changes on ``main`` that should *not* go into the release. We
+don't generally allow experimental changes onto ``main``. Anything
+accepted onto ``main`` should be intended for the next minor/major
+(not _patch_)release.
 
-10. Draft the release on the GitHub
-    [Releases](https://github.com/AcademySoftwareFoundation/openexr/releases)
-    page.  Include the summary from the notes in
-    [CHANGES.md](CHANGES.md), but don't include the list of PR's.
+The overall workflow is similar to a patch release, as described
+above, but it's simpler because there is no cherry-picking of
+commits. The major/minor release is simply a snapshot of ``main``.
 
-    - Create the release from the latest ``--rc`` tag, and give it a name
-      that begins with ``v`` and ends in ``0``, e.g. ``v3.2.0``.
+To create a new release from the ``main`` branch, start by creating
+the new "release branch", e.g. `RB-3.5` from the tip of `main`. Then
+follow the same instructions above as for a patch release.
 
-    - Save the release as a "draft".
+Assuming all PRs that have _not_ been merged into a patch release are
+labeled with the new minor release, e.g. `v3.5`, the helper scripts in
+`share/util/release` will automate the various steps in generating the
+release notes, drafting the release, and helping with the various
+announcements. 
 
-11. Wait at least 48 hours after the email announcement.
+In addition to the above:
+    
+1. Increment the ``OPENEXR_LIB_SOVERSION`` setting in [CMakeLists.txt](CMakeLists.txt).
 
-12. Publish the release
+   The SO version increases whenever, and only when, the ABI changes
+   in non-backwards-compatible ways. Consistent with the semantic
+   versioning policy, this usually happens at major and minor
+   releases, but never on a patch release.
 
-    a. Click the "Publish release" button on the GitHub release draft
+   Commit this change to the `RB-` branch.
 
-    b. Send an email to ``openexr-dev@lists.aswf.io`` officially
-       announcing the release.
 
-13. Update the ``release`` branch, which should always point to the
-    most recent release.
+2. Confirm that the ``OPENEXR_VERSION_MAJOR``,
+   ``OPENEXR_VERSION_MINOR``, and ``OPENEXR_VERSION_PATCH`` value in
+   [src/lib/OpenEXRCore/openexr_version.h](src/lib/OpenEXRCore/openexr_version.h)
+   are correct on the main branch. The OpenEXR project policy is that
+   the values on the main branch, which is the bleeding edge of
+   development, correspond to the next minor release, with the patch
+   set to 0.
 
-    From a clone of the main repo:
+3. Update the ``IMATH_TAG`` setting in
+   [cmake/OpenEXRSetup.cmake](cmake/OpenEXRSetup.cmake) to correspond
+   to the proper Imath release.
 
-        % git checkout release
-        % git merge RB-3.1
-        % git push
-         
-14. Increment ``OPENEXR_VERSION_MINOR`` in
-    [src/lib/OpenEXRCore/openexr_version.h](src/lib/OpenEXRCore/openexr_version.h) on the main branch
-
-    - Submit a PR for this. This leaves the release version on the
-      main branch pointing to the next minor release, as described in
-      Step #1.
-
-15. Build the website at https://readthedocs.org/projects/openexr.
-
-16. If the release has resolved any OSS-Fuzz issues, update the
-    associated pages at https://bugs.chromium.org/p/oss-fuzz with a
-    reference to the release.
-
-17. If the release has resolved any public CVE's, request an update
-    from the registry service providing the release and a link to the
-    release notes.
diff -Nru openexr-3.4.6+ds/debian/changelog openexr-3.4.14/debian/changelog
--- openexr-3.4.6+ds/debian/changelog	2026-03-18 19:39:57.000000000 +0200
+++ openexr-3.4.14/debian/changelog	2026-08-20 19:49:50.000000000 +0300
@@ -1,3 +1,41 @@
+openexr (3.4.14-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * New upstream release. (Closes: #1136879)
+    - Fixes FTBFS with glibc 2.43. (Closes: #1144878)
+    - CVE-2026-34378: Signed integer overflow in generic_unpack()
+    - CVE-2026-34379: Misaligned write in LossyDctDecoder_execute
+    - CVE-2026-34380: Signed integer overflow in undo_pxr24_impl()
+    - CVE-2026-34588: Signed 32-bit Overflow in PIZ Decoder
+    - CVE-2026-34589: DWA Lossy Decoder Heap Out-of-Bounds Write
+    (Closes: #1133188)
+    - CVE-2026-34543: Heap information disclosure in PXR24 decompression
+      (Closes: #1132580)
+    - CVE-2026-34544: Integer overflow in uncompress_b44_impl()
+      (Closes: #1132579)
+    - CVE-2026-34545: Integer overflow in HTJ2K decoder
+      (Closes: #1132578)
+    - CVE-2026-39886: Signed Integer Overflow in ht_undo_impl()
+    - CVE-2026-40244: Integer overflow in DWA setupChannelData
+    - CVE-2026-40250: Integer overflow in DWA decoder
+    (Closes: #1134642)
+    - CVE-2026-41142: Integer overflow in ImageChannel::resize()
+      (Closes: #1135946)
+    - CVE-2026-42216: Out-of-bounds read in IDManifest::init()
+    - CVE-2026-42217: Shift exponent overflow in readVariableLengthInteger()
+    (Closes: #1136001)
+    - CVE-2026-44663: Integer overflow in HTJ2K decoder
+    - CVE-2026-45696: Heap buffer overflow in ht_undo_impl()
+  * Generate Provides that includes the Imath version and use in the shlibs.
+    (Closes: #1144763)
+  * Disable test that fails on armhf instead of ignoring all test failures
+    on armhf.
+  * Ignore test failures on all big endian architectures.
+  * Attempt to fix FTBFS on hurd-amd64.
+  * Ignore test failures on Hurd.
+
+ -- Adrian Bunk <bunk@debian.org>  Thu, 20 Aug 2026 19:49:50 +0300
+
 openexr (3.4.6+ds-4) unstable; urgency=medium
 
   * Team upload
diff -Nru openexr-3.4.6+ds/debian/control openexr-3.4.14/debian/control
--- openexr-3.4.6+ds/debian/control	2026-03-18 19:37:53.000000000 +0200
+++ openexr-3.4.14/debian/control	2026-08-20 19:49:50.000000000 +0300
@@ -24,6 +24,7 @@
 Depends: ${misc:Depends}, ${shlibs:Depends}
 Breaks: libopenexr-3-1-30 (<< 3.4.6+ds-3)
 Replaces: libopenexr-3-1-30 (<< 3.4.6+ds-3)
+Provides: ${libopenexr:Provides}
 Description: runtime files for the OpenEXR image library
  OpenEXR is a high dynamic-range (HDR) image file format developed by
  Industrial Light & Magic for use in computer imaging applications.
diff -Nru openexr-3.4.6+ds/debian/patches/hack-test-armhf.patch openexr-3.4.14/debian/patches/hack-test-armhf.patch
--- openexr-3.4.6+ds/debian/patches/hack-test-armhf.patch	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/debian/patches/hack-test-armhf.patch	2026-08-20 19:49:50.000000000 +0300
@@ -0,0 +1,16 @@
+Description: Disable test that fails on armhf
+Author: Adrian Bunk <bunk@debian.org>
+Bug: https://github.com/AcademySoftwareFoundation/openexr/issues/2134#issuecomment-4223184412
+Forwarded: not-needed
+
+--- openexr-3.4.14.orig/src/test/OpenEXRTest/main.cpp
++++ openexr-3.4.14/src/test/OpenEXRTest/main.cpp
+@@ -181,7 +181,7 @@ main (int argc, char* argv[])
+     TEST (testWav, "core");
+     TEST (testRgba, "basic");
+     TEST (testCRgba, "basic");
+-    TEST (testLargeDataWindowOffsets, "basic");
++    //TEST (testLargeDataWindowOffsets, "basic");
+     TEST (testSharedFrameBuffer, "basic");
+     TEST (testRgbaThreading, "basic");
+     TEST (testChannels, "basic");
diff -Nru openexr-3.4.6+ds/debian/patches/hurd-no-sa_restorer.patch openexr-3.4.14/debian/patches/hurd-no-sa_restorer.patch
--- openexr-3.4.6+ds/debian/patches/hurd-no-sa_restorer.patch	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/debian/patches/hurd-no-sa_restorer.patch	2026-08-20 19:49:50.000000000 +0300
@@ -0,0 +1,14 @@
+Description: sigaction(2) says that sa_restorer should not be used
+ This also caused a FTBFS on Hurd.
+Author: Adrian Bunk <bunk@debian.org>
+
+--- openexr-3.4.14.orig/src/lib/Iex/IexMathFpu.cpp
++++ openexr-3.4.14/src/lib/Iex/IexMathFpu.cpp
+@@ -420,7 +420,6 @@ setFpExceptionHandler (FpExceptionHandle
+         sigemptyset (&action.sa_mask);
+         action.sa_flags     = SA_SIGINFO | SA_NOMASK;
+         action.sa_sigaction = (void (*) (int, siginfo_t*, void*)) catchSigFpe;
+-        action.sa_restorer  = 0;
+ 
+         sigaction (SIGFPE, &action, 0);
+     }
diff -Nru openexr-3.4.6+ds/debian/patches/series openexr-3.4.14/debian/patches/series
--- openexr-3.4.6+ds/debian/patches/series	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/debian/patches/series	2026-08-20 19:49:50.000000000 +0300
@@ -0,0 +1,2 @@
+hack-test-armhf.patch
+hurd-no-sa_restorer.patch
diff -Nru openexr-3.4.6+ds/debian/rules openexr-3.4.14/debian/rules
--- openexr-3.4.6+ds/debian/rules	2026-03-18 19:37:53.000000000 +0200
+++ openexr-3.4.14/debian/rules	2026-08-20 19:49:50.000000000 +0300
@@ -5,10 +5,24 @@
 
 INSTDIR = debian/tmp
 
+LIB_PKG = $(shell dh_listpackages | grep libopenexr | grep -v libopenexr-d)
+ABI_PKG = $(LIB_PKG)-imath$(shell pkgconf --variable=libsuffix Imath | sed 's/_/-/g')
+
 %:
 	dh $@ -Bbuild -Scmake
 
-ifneq (,$(filter armhf s390x,$(DEB_HOST_ARCH)))
+override_dh_makeshlibs:
+	echo "libopenexr:Provides=$(ABI_PKG)" >> debian/$(LIB_PKG).substvars
+	dh_makeshlibs -p$(LIB_PKG) -V "$(ABI_PKG)"
+	dh_makeshlibs --remaining-packages
+
+ifeq ($(DEB_HOST_ARCH_ENDIAN),big)
+# Ignore test results
+override_dh_auto_test:
+	-dh_auto_test
+endif
+
+ifeq ($(DEB_HOST_ARCH_OS),hurd)
 # Ignore test results
 override_dh_auto_test:
 	-dh_auto_test
diff -Nru openexr-3.4.6+ds/.github/dependabot.yml openexr-3.4.14/.github/dependabot.yml
--- openexr-3.4.6+ds/.github/dependabot.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/dependabot.yml	2026-08-05 04:29:55.000000000 +0300
@@ -11,12 +11,18 @@
       interval: "daily"
     exclude-paths:
       - ".github/workflows/ci_workflow_old.yml"
-      
+
   - package-ecosystem: "pip"
-    directory: "/"
+    directory: "/website"
     schedule:
       day: "monday"
       interval: "weekly"
+    # Hashed lockfile is regenerated locally with pip-compile; do not let
+    # Dependabot rewrite it (transitive bumps belong in requirements.txt or
+    # a deliberate lock regen, not automated lock-only PRs).
+    exclude-paths:
+      - "website/requirements-lock.txt"
+      - "website/requirements.txt.lock"
 
   - package-ecosystem: "gitsubmodule"
     directory: "/"
@@ -24,7 +30,7 @@
       day: "monday"
       interval: "weekly"
 
-  - package-ecosystem: "bazel" 
+  - package-ecosystem: "bazel"
     directory: "/"
     schedule:
       interval: "weekly"
\ No newline at end of file
diff -Nru openexr-3.4.6+ds/.github/workflows/analysis_workflow.yml openexr-3.4.14/.github/workflows/analysis_workflow.yml
--- openexr-3.4.6+ds/.github/workflows/analysis_workflow.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/analysis_workflow.yml	2026-08-05 04:29:55.000000000 +0300
@@ -29,17 +29,13 @@
     container:
       # DockerHub: https://hub.docker.com/u/aswf
       # Source: https://github.com/AcademySoftwareFoundation/aswf-docker
-      image: aswf/ci-openexr:2024
+      image: aswf/ci-openexr:2026
     env:
       CXX: g++
       CC: gcc
     steps:
-      # TODO: Remove this workaround following resolution of:
-      #       https://github.com/AcademySoftwareFoundation/aswf-docker/issues/43
-      - name: Setup container
-        run: sudo rm -rf /usr/local/lib64/cmake/glew
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
         with:
           fetch-depth: 50
       - name: Create build directories
@@ -109,7 +105,7 @@
       - name: Setup container
         run: sudo rm -rf /usr/local/lib64/cmake/glew
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
         with:
           fetch-depth: 50
       - name: Create build directories
@@ -188,7 +184,7 @@
       - name: Setup container
         run: sudo rm -rf /usr/local/lib64/cmake/glew
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
         with:
           fetch-depth: 50
       - name: Create build directories
diff -Nru openexr-3.4.6+ds/.github/workflows/bazel_build.yml openexr-3.4.14/.github/workflows/bazel_build.yml
--- openexr-3.4.6+ds/.github/workflows/bazel_build.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/bazel_build.yml	2026-08-05 04:29:55.000000000 +0300
@@ -39,10 +39,10 @@
     runs-on: ubuntu-24.04
     
     steps:
-    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
     - name: Mount Bazel cache
-      uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
+      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
       with:
         path: "/home/runner/.cache/bazel"
         key: bazel-ubuntu-24
@@ -57,10 +57,10 @@
     runs-on: windows-2025
 
     steps:
-    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
     - name: Mount Bazel cache
-      uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
+      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
       with:
         path: "/home/runner/.cache/bazel"
         key: bazel-windows-2025
@@ -75,10 +75,10 @@
     runs-on: macos-15
 
     steps:
-    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
     - name: Mount Bazel cache
-      uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
+      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
       with:
         path: "/home/runner/.cache/bazel"
         key: bazel-macos-15
diff -Nru openexr-3.4.6+ds/.github/workflows/ci_freebsd.yml openexr-3.4.14/.github/workflows/ci_freebsd.yml
--- openexr-3.4.6+ds/.github/workflows/ci_freebsd.yml	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/.github/workflows/ci_freebsd.yml	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) Contributors to the OpenEXR Project.
+#
+# Build and test on FreeBSD using a community VM action.
+# GitHub does not provide native FreeBSD runners; this uses vmactions/freebsd-vm.
+# Validates the pthread fallback in internal_thread.h (issue #2299).
+
+name: CI (FreeBSD)
+
+on:
+  push:
+    paths:
+      - '**'
+      - '!**.md'
+      - '!website/**'
+      - '!bazel/**'
+      - '!src/wrappers/**'
+      - '!.github/workflows/**'
+      - '.github/workflows/ci_freebsd.yml'
+  pull_request:
+    paths:
+      - '**'
+      - '!**.md'
+      - '!website/**'
+      - '!bazel/**'
+      - '!src/wrappers/**'
+      - '!.github/workflows/**'
+      - '.github/workflows/ci_freebsd.yml'
+
+permissions:
+  contents: read
+
+jobs:
+  freebsd:
+    name: FreeBSD
+    runs-on: ubuntu-latest
+    steps:
+      - name: Build and test on FreeBSD
+        uses: vmactions/freebsd-vm@77ed28d336d03fe19a3f4f7266c1d2c4714dd79d  # v1.5.2
+        with:
+          release: '15.0'
+          envs: GITHUB_REPOSITORY GITHUB_REF GITHUB_SHA
+          usesh: true
+          prepare: |
+            pkg install -y cmake ninja git
+          run: |
+            set -e
+            git clone --depth 1 "https://github.com/${GITHUB_REPOSITORY}.git" src
+            cd src
+            # shallow clone, only the commit that triggered the run, i.e. GITHUB_SHA
+            git fetch --depth 1 origin "${GITHUB_SHA}"
+            git checkout "${GITHUB_SHA}"
+            cmake -B _build -S . -G Ninja \
+              -DCMAKE_BUILD_TYPE=Release \
+              -DOPENEXR_FORCE_INTERNAL_IMATH=ON \
+              -DOPENEXR_FORCE_INTERNAL_DEFLATE=ON \
+              -DOPENEXR_FORCE_INTERNAL_OPENJPH=ON \
+              -DBUILD_TESTING=ON
+            cmake --build _build
+            ctest --test-dir _build --output-on-failure
diff -Nru openexr-3.4.6+ds/.github/workflows/ci_steps.yml openexr-3.4.14/.github/workflows/ci_steps.yml
--- openexr-3.4.6+ds/.github/workflows/ci_steps.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/ci_steps.yml	2026-08-05 04:29:55.000000000 +0300
@@ -64,10 +64,16 @@
       validate_install:
         type: string
 
+permissions:
+  contents: read
+
 jobs:
   steps:
     runs-on: ${{ inputs.os }}
 
+    permissions:
+      contents: write  # required for actions/upload-artifact
+
     container:
       image: ${{ inputs.container }}
 
@@ -80,7 +86,7 @@
     steps:
 
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
       - name: Set Up Directories & Environment
         run: |
@@ -120,17 +126,33 @@
 
         shell: bash
 
+      - name: Report C Runtime Version
+        run: |
+          if [[ "$RUNNER_OS" == "Linux" ]]; then
+            ldd --version 2>&1 | head -1 || true
+          elif [[ "$RUNNER_OS" == "macOS" ]]; then
+            echo "macOS SDK: $(xcrun --show-sdk-version 2>/dev/null || echo 'unknown')"
+          fi
+        shell: bash
+
+      - name: Install Build Dependencies (Fedora)
+        if: startsWith(inputs.container, 'fedora:')
+        run: |
+          dnf install -y gcc gcc-c++ cmake make git wget libdeflate-devel
+        shell: bash
+
       - name: Determine MSYS2 Packages
         if: inputs.msystem != ''
         run: |
           PACKAGES="cc:p cmake:p"
-          if [ "${{ inputs.OPENEXR_FORCE_INTERNAL_IMATH }}" == "OFF" ]; then
+          # mingw32 has no package for imath and openjph
+          if [[ "${{ inputs.OPENEXR_FORCE_INTERNAL_IMATH }}" == "OFF" && "${{ inputs.msystem }}" != "MINGW32" ]]; then
             PACKAGES="$PACKAGES imath:p"
           fi
           if [ "${{ inputs.OPENEXR_FORCE_INTERNAL_DEFLATE }}" == "OFF" ]; then
             PACKAGES="$PACKAGES libdeflate:p"
           fi
-          if [ "${{ inputs.OPENEXR_FORCE_INTERNAL_OPENJPH }}" == "OFF" && "${{ inputs.msystem }}" != "MINGW32" ]; then
+          if [[ "${{ inputs.OPENEXR_FORCE_INTERNAL_OPENJPH }}" == "OFF" && "${{ inputs.msystem }}" != "MINGW32" ]]; then
             PACKAGES="$PACKAGES openjph:p"
           fi
           echo "PACBOY_PACKAGES=$PACKAGES" >> $GITHUB_ENV
@@ -138,7 +160,7 @@
 
       - name: Install MSYS2 ${{ inputs.msystem }}
         if: inputs.msystem != ''
-        uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0
+        uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0
         with:
           msystem: ${{ inputs.msystem }}
           update: true
@@ -177,6 +199,14 @@
           echo "OPENEXR_PATH=$OPENEXR_PATH:$PROGRAM_FILES/Imath/bin:$PROGRAM_FILES/Imath/lib" >> $GITHUB_ENV
         shell: bash
 
+      - name: Install Imath (msys2)
+        # mingw32 has no package for imath, so install manually
+        if: inputs.OPENEXR_FORCE_INTERNAL_IMATH == 'OFF' && inputs.msystem == 'MINGW32'
+        run: |
+          set -x
+          share/ci/scripts/install_imath.sh main
+        shell: msys2 {0}
+
       - name: Install libdeflate
         # Pre-install libdeflate so the builds validate that find_package sees the external installation
         if: inputs.OPENEXR_FORCE_INTERNAL_DEFLATE == 'OFF' && inputs.msystem == ''
@@ -195,6 +225,14 @@
           echo "OPENEXR_PATH=$OPENEXR_PATH:$PROGRAM_FILES/openjph/bin:$PROGRAM_FILES/openjph/lib" >> $GITHUB_ENV
         shell: bash
 
+      - name: Install OpenJPH (msys2)
+        # mingw32 has no package for openjph, so install manually
+        if: inputs.OPENEXR_FORCE_INTERNAL_OPENJPH == 'OFF' && inputs.msystem == 'MINGW32'
+        run: |
+          set -x
+          share/ci/scripts/install_openjph.sh 0.22.0
+        shell: msys2 {0}
+
       - name: Install help2man
         # TODO: this could go in the ASWF Linux docker
         # container. Also, it doesn't currently work for Windows, so
@@ -297,7 +335,7 @@
 
       - name: Upload install_manifest.txt
         # Upload the manifest to make it possible to download for inspection and debugging
-        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
           name: ${{ env.INSTALL_MANIFEST_FILENAME }}
           path: ${{ env.INSTALL_MANIFEST_PATH }}
diff -Nru openexr-3.4.6+ds/.github/workflows/ci_workflow_old.yml openexr-3.4.14/.github/workflows/ci_workflow_old.yml
--- openexr-3.4.6+ds/.github/workflows/ci_workflow_old.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/ci_workflow_old.yml	2026-08-05 04:29:55.000000000 +0300
@@ -64,6 +64,9 @@
         - /node20217:/node20217:rw,rshared
         - /node20217:/__e/node20:ro,rshared
 
+    env:
+      ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
+
     strategy:
       matrix:
         include:
diff -Nru openexr-3.4.6+ds/.github/workflows/ci_workflow.yml openexr-3.4.14/.github/workflows/ci_workflow.yml
--- openexr-3.4.6+ds/.github/workflows/ci_workflow.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/ci_workflow.yml	2026-08-05 04:29:55.000000000 +0300
@@ -56,6 +56,8 @@
 jobs:
   linux:
     name: 'Linux.${{ matrix.build}}: ${{ matrix.label }}'
+    permissions:
+      contents: write  # required by ci_steps (upload-artifact)
     uses: ./.github/workflows/ci_steps.yml
     with:
       # Set values for the "inputs:" as defined in ci_steps.yml
@@ -65,7 +67,7 @@
       build: ${{ matrix.build }}
       # DockerHub: https://hub.docker.com/u/aswf
       # Source: https://github.com/AcademySoftwareFoundation/aswf-docker
-      container: aswf/ci-openexr:${{ matrix.vfx-cy || '2024' }}
+      container: ${{ matrix.container || format('aswf/ci-openexr:{0}', matrix.vfx-cy || '2024') }}
       cmake: ${{ matrix.cmake || '3.14.7' }}
       cxx-standard: ${{ matrix.cxx-standard || '17' }}
       cxx-compiler: ${{ matrix.cxx-compiler || 'g++' }}
@@ -147,8 +149,26 @@
             RUN_TESTING: OFF
             validate_install: OFF
 
+          - build: 12
+            label: Fedora Rawhide
+            container: fedora:rawhide
+            cmake: '3.31.6'
+            OPENEXR_FORCE_INTERNAL_IMATH: 'ON'
+            OPENEXR_FORCE_INTERNAL_DEFLATE: 'OFF'
+            OPENEXR_FORCE_INTERNAL_OPENJPH: 'ON'
+            OPENEXR_INSTALL_DOCS: 'OFF'
+            validate_install: 'OFF'
+
+          - build: 13
+            label: STATIC w/vendored OpenJPH
+            OPENEXR_FORCE_INTERNAL_OPENJPH: 'ON'
+            BUILD_SHARED_LIBS: 'OFF'
+            RUN_TESTING: OFF
+
   macOS:
     name: 'macOS.${{ matrix.build}}: ${{ matrix.label }}'
+    permissions:
+      contents: write  # required by ci_steps (upload-artifact)
     uses: ./.github/workflows/ci_steps.yml
     with:
       # Set values for the "inputs:" as defined in ci_steps.yml
@@ -233,6 +253,8 @@
 
   windows:
     name: 'Windows.${{ matrix.build}}: ${{ matrix.label }}'
+    permissions:
+      contents: write  # required by ci_steps (upload-artifact)
     uses: ./.github/workflows/ci_steps.yml
     with:
       # Set values for the "inputs:" as defined in ci_steps.yml
diff -Nru openexr-3.4.6+ds/.github/workflows/codeql.yml openexr-3.4.14/.github/workflows/codeql.yml
--- openexr-3.4.6+ds/.github/workflows/codeql.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/codeql.yml	2026-08-05 04:29:55.000000000 +0300
@@ -22,6 +22,9 @@
   schedule:
     - cron: '37 10 * * 0'
 
+permissions:
+  contents: read
+
 jobs:
   analyze:
     name: Analyze (${{ matrix.language }})
@@ -58,14 +61,14 @@
         # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
     steps:
     - name: Checkout repository
-      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+      uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
       # cary: Pin the version to the SHA for 2.18.0, since there appears to
       # be a problem with 2.18.1 leading to a "No space left on
       # device" failure
-      uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
+      uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
       with:
         # cary: the "linked" setting is necessary to force the run to pick up
         # the version specified in the action.
@@ -96,6 +99,11 @@
 
     - name: Perform CodeQL Analysis
       # Pin the version to the SHA for 2.18.0 
-      uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
+      uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
       with:
         category: "/language:${{matrix.language}}"
+        # For pull_request, upload against refs/pull/<n>/head + head SHA so the Code
+        # Scanning API does not require refs/heads/<branch> (e.g. Dependabot branches
+        # can trigger "ref ... not found"). Ignored for fork PRs by the action.
+        ref: ${{ github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
+        sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
diff -Nru openexr-3.4.6+ds/.github/workflows/ossfuzz_workflow.yml openexr-3.4.14/.github/workflows/ossfuzz_workflow.yml
--- openexr-3.4.6+ds/.github/workflows/ossfuzz_workflow.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/ossfuzz_workflow.yml	2026-08-05 04:29:55.000000000 +0300
@@ -48,7 +48,7 @@
         dry-run: false
         language: c++
     - name: Upload Crash
-      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+      uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
       if: failure() && steps.build.outcome == 'success'
       with:
         name: artifacts
diff -Nru openexr-3.4.6+ds/.github/workflows/python-wheels-publish-test.yml openexr-3.4.14/.github/workflows/python-wheels-publish-test.yml
--- openexr-3.4.6+ds/.github/workflows/python-wheels-publish-test.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/python-wheels-publish-test.yml	2026-08-05 04:29:55.000000000 +0300
@@ -13,12 +13,47 @@
       - v3.[0-9]+.[0-9]+-rc*
   workflow_dispatch:
 
+concurrency:
+  group: testpypi-publish-${{ github.ref }}
+  cancel-in-progress: false
+
 permissions:
   contents: read
 
 jobs:
+  guard:
+    name: Verify RB-* release ref
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+        with:
+          fetch-depth: 0
+          ref: ${{ github.ref }}
+
+      - name: Verify RB-* release ref
+        env:
+          EVENT_NAME: ${{ github.event_name }}
+          REF_NAME: ${{ github.ref_name }}
+        run: |
+          set -euo pipefail
+
+          if [[ "$EVENT_NAME" == "workflow_dispatch" && "$REF_NAME" != RB-* ]]; then
+            echo "::error::workflow_dispatch must use an RB-* branch (not $REF_NAME)."
+            exit 1
+          fi
+
+          git fetch origin '+refs/heads/RB-*:refs/remotes/origin/RB-*' || git fetch origin
+
+          if ! git branch -r --contains HEAD | grep -E -q 'origin/RB-'; then
+            echo "::error::Ref must be on an RB-* release branch (not main)."
+            git branch -r --contains HEAD || true
+            exit 1
+          fi
+
   build:
     name: Python Wheels - ${{ matrix.os }}-${{ matrix.arch }}
+    needs: guard
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
@@ -29,24 +64,24 @@
             arch: arm64
           - os: macos-15-intel
             arch: x64
+            cibw_archs_macos: x86_64 universal2
           - os: macos-latest
             arch: arm64
+            cibw_archs_macos: arm64
           - os: windows-latest
             arch: x64
 
-    environment:
-      name: testpypi
-      url: https://test.pypi.org/p/openexr
-
     permissions:
-      id-token: write
-      
+      contents: read
+      actions: write
+
     steps:
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
-      - name: Install Python 
-        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+      - name: Install Python
+        if: ${{ matrix.os == 'ubuntu-latest' }}
+        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
         with:
           python-version: '3.x'
 
@@ -55,25 +90,25 @@
         if: ${{ matrix.os == 'ubuntu-latest' }}
         env:
           OPENEXR_RELEASE_CANDIDATE_TAG: ${{ github.ref_name }}
-        run: pipx run build --sdist . --outdir wheelhouse
+        run: pipx run build==1.2.2.post1 --sdist . --outdir wheelhouse
 
       - name: Build wheel
-        uses: pypa/cibuildwheel@v3.3
+        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084  # v3.4.1
         with:
           output-dir: wheelhouse
         env:
           MACOSX_DEPLOYMENT_TARGET: 10.15
-          CIBW_ARCHS_MACOS: x86_64 arm64 universal2
+          # Split macOS arches across runners so artifact merge does not collide on filenames.
+          CIBW_ARCHS_MACOS: ${{ matrix.cibw_archs_macos }}
           # Build Python 3.8 through 3.13.
           # Skip 32-bit wheels builds on Windows
           # Also skip the PyPy builds, since they fail the unit tests
           CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*"
           CIBW_SKIP: "*-win32 *_i686"
-          CIBW_TEST_SKIP: "*-macosx_universal2:arm64"
           CIBW_ENVIRONMENT: OPENEXR_RELEASE_CANDIDATE_TAG="${{ github.ref_name }}"
 
       - name: Upload artifact
-        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
           name: wheels-${{ matrix.os }}-${{ matrix.arch }}
           path: |
@@ -91,41 +126,19 @@
       url: https://test.pypi.org/p/openexr
 
     permissions:
+      contents: read
+      actions: read
       id-token: write
 
     steps:
+      - name: Download wheel artifacts
+        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+        with:
+          pattern: wheels-*
+          merge-multiple: true
+          path: dist
 
-    - name: Download ubuntu-latest-x64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-ubuntu-latest-x64
-        path: dist
-
-    - name: Download ubuntu-24.04-arm-arm64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-ubuntu-24.04-arm-arm64
-        path: dist
-
-    - name: Download macos-15-intel-x64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-macos-15-intel-x64
-        path: dist
-
-    - name: Download macos-latest-arm64
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-macos-latest-arm64
-        path: dist
-
-    - name: Download windows-latest-x64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-windows-latest-x64
-        path: dist
-
-    - name: Publish distribution 📦 to TestPyPI
-      uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
-      with:
-        repository-url: https://test.pypi.org/legacy/
+      - name: Publish distribution 📦 to TestPyPI
+        uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
+        with:
+          repository-url: https://test.pypi.org/legacy/
diff -Nru openexr-3.4.6+ds/.github/workflows/python-wheels-publish.yml openexr-3.4.14/.github/workflows/python-wheels-publish.yml
--- openexr-3.4.6+ds/.github/workflows/python-wheels-publish.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/python-wheels-publish.yml	2026-08-05 04:29:55.000000000 +0300
@@ -8,13 +8,48 @@
   release:
     types: [published]
   workflow_dispatch:
-  
+
+concurrency:
+  group: pypi-publish-${{ github.event.release.tag_name || github.ref }}
+  cancel-in-progress: false
+
 permissions:
   contents: read
 
 jobs:
+  guard:
+    name: Verify RB-* release ref
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+        with:
+          fetch-depth: 0
+          ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref }}
+
+      - name: Verify RB-* release ref
+        env:
+          EVENT_NAME: ${{ github.event_name }}
+          REF_NAME: ${{ github.ref_name }}
+        run: |
+          set -euo pipefail
+
+          if [[ "$EVENT_NAME" == "workflow_dispatch" && "$REF_NAME" != RB-* ]]; then
+            echo "::error::workflow_dispatch must use an RB-* branch (not $REF_NAME)."
+            exit 1
+          fi
+
+          git fetch origin '+refs/heads/RB-*:refs/remotes/origin/RB-*' || git fetch origin
+
+          if ! git branch -r --contains HEAD | grep -E -q 'origin/RB-'; then
+            echo "::error::Ref must be on an RB-* release branch (not main)."
+            git branch -r --contains HEAD || true
+            exit 1
+          fi
+
   build:
     name: Python Wheels - ${{ matrix.os }}-${{ matrix.arch }}
+    needs: guard
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
@@ -25,48 +60,48 @@
             arch: arm64
           - os: macos-15-intel
             arch: x64
+            cibw_archs_macos: x86_64 universal2
           - os: macos-latest
             arch: arm64
+            cibw_archs_macos: arm64
           - os: windows-latest
             arch: x64
 
-    environment:
-      name: pypi
-      url: https://pypi.org/p/openexr
-
     permissions:
-      id-token: write
+      contents: read
+      actions: write
 
     steps:
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
-      - name: Install Python 
-        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+      - name: Install Python
+        if: ${{ matrix.os == 'ubuntu-latest' }}
+        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
         with:
           python-version: '3.x'
 
       - name: Create sdist
         # Only create it once.
         if: ${{ matrix.os == 'ubuntu-latest' }}
-        run: pipx run build --sdist . --outdir wheelhouse
+        run: pipx run build==1.2.2.post1 --sdist . --outdir wheelhouse
 
       - name: Build wheel
-        uses: pypa/cibuildwheel@v3.3
+        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084  # v3.4.1
         with:
           output-dir: wheelhouse
         env:
           MACOSX_DEPLOYMENT_TARGET: 10.15
-          CIBW_ARCHS_MACOS: x86_64 arm64 universal2
+          # Split macOS arches across runners so artifact merge does not collide on filenames.
+          CIBW_ARCHS_MACOS: ${{ matrix.cibw_archs_macos }}
           # Build Python 3.8 through 3.13
           # Skip 32-bit wheels builds on Windows
           # Also skip the PyPy builds, since they fail the unit tests
           CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*"
           CIBW_SKIP: "*-win32 *_i686"
-          CIBW_TEST_SKIP: "*arm64"
 
       - name: Upload artifact
-        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
           name: wheels-${{ matrix.os }}-${{ matrix.arch }}
           path: |
@@ -84,39 +119,17 @@
       url: https://pypi.org/p/openexr
 
     permissions:
+      contents: read
+      actions: read
       id-token: write
 
     steps:
+      - name: Download wheel artifacts
+        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+        with:
+          pattern: wheels-*
+          merge-multiple: true
+          path: dist
 
-    - name: Download ubuntu-latest-x64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-ubuntu-latest-x64
-        path: dist
-
-    - name: Download ubuntu-24.04-arm-arm64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-ubuntu-24.04-arm-arm64
-        path: dist
-
-    - name: Download macos-15-intel-x64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-macos-15-intel-x64
-        path: dist
-
-    - name: Download macos-latest-arm64
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-macos-latest-arm64
-        path: dist
-
-    - name: Download windows-latest-x64 artifacts
-      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
-      with:
-        name: wheels-windows-latest-x64
-        path: dist
-
-    - name: Publish distribution 📦 to PyPI
-      uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
+      - name: Publish distribution 📦 to PyPI
+        uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
diff -Nru openexr-3.4.6+ds/.github/workflows/python-wheels.yml openexr-3.4.14/.github/workflows/python-wheels.yml
--- openexr-3.4.6+ds/.github/workflows/python-wheels.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/python-wheels.yml	2026-08-05 04:29:55.000000000 +0300
@@ -48,41 +48,107 @@
             arch: arm64
           - os: macos-15-intel
             arch: x64
+            cibw_archs_macos: x86_64 universal2
           - os: macos-latest
             arch: arm64
+            cibw_archs_macos: arm64
           - os: windows-latest
             arch: x64
 
-    steps:
+    permissions:
+      contents: read
+      actions: write
 
+    steps:
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
-      - name: Install Python 
-        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+      - name: Install Python
+        if: ${{ matrix.os == 'ubuntu-latest' }}
+        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
         with:
           python-version: '3.x'
 
       - name: Create sdist
         # Only create it once.
         if: ${{ matrix.os == 'ubuntu-latest' }}
-        run: pipx run build --sdist . --outdir wheelhouse
+        run: pipx run build==1.2.2.post1 --sdist . --outdir wheelhouse
 
       - name: Build wheel
-        uses: pypa/cibuildwheel@v3.3
+        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084  # v3.4.1
+        with:
+          output-dir: wheelhouse
         env:
-          CIBW_ARCHS_MACOS: x86_64 arm64 universal2
+          # Split macOS arches across runners so artifact merge does not collide on filenames.
+          CIBW_ARCHS_MACOS: ${{ matrix.cibw_archs_macos }}
           # Build Python 3.8 through 3.13
           # Skip 32-bit wheels builds on Windows
           # Also skip the PyPy builds, since they fail the unit tests
           CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*"
           CIBW_SKIP: "*-win32 *_i686"
-          CIBW_TEST_SKIP: "*-macosx*arm64"
 
       - name: Upload artifact
-        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
           name: wheels-${{ matrix.os }}-${{ matrix.arch }}
           path: |
             ./wheelhouse/*.whl
-            ./wheelhouse/*.tar.gz
\ No newline at end of file
+            ./wheelhouse/*.tar.gz
+
+  verify_publish_artifacts:
+    name: Verify merged wheel artifacts
+    needs: build_wheels
+    runs-on: ubuntu-latest
+
+    permissions:
+      contents: read
+      actions: read
+
+    steps:
+      - name: Download wheel artifacts separately
+        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+        with:
+          pattern: wheels-*
+          path: artifacts
+
+      - name: Detect duplicate distribution filenames
+        run: |
+          set -euo pipefail
+
+          mapfile -t dupes < <(
+            find artifacts -type f \( -name '*.whl' -o -name '*.tar.gz' \) -exec basename {} \; \
+              | sort \
+              | uniq -d
+          )
+
+          if ((${#dupes[@]} > 0)); then
+            echo "::error::Duplicate distribution filenames across build artifacts:"
+            printf '  %s\n' "${dupes[@]}"
+            exit 1
+          fi
+
+      - name: Download wheel artifacts (as publish does)
+        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+        with:
+          pattern: wheels-*
+          merge-multiple: true
+          path: dist
+
+      - name: Verify wheel ZIP integrity
+        run: |
+          set -euo pipefail
+          shopt -s nullglob
+
+          wheels=(dist/*.whl)
+          if ((${#wheels[@]} == 0)); then
+            echo "::error::No wheels found in dist/"
+            exit 1
+          fi
+
+          for wheel in "${wheels[@]}"; do
+            echo "Checking $wheel"
+            python3 -m zipfile -t "$wheel"
+          done
+
+      - name: Validate distribution metadata
+        run: pipx run twine==6.1.0 check dist/*
diff -Nru openexr-3.4.6+ds/.github/workflows/release-notice.yml openexr-3.4.14/.github/workflows/release-notice.yml
--- openexr-3.4.6+ds/.github/workflows/release-notice.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/release-notice.yml	2026-08-05 04:29:55.000000000 +0300
@@ -25,7 +25,7 @@
         slack_bot_token: ${{ secrets.SLACK_BOT_TOKEN }}
         slack_channel: "#release-announcements"
         project_logo: "https://artwork.aswf.io/projects/openexr/icon/color/openexr-icon-color.png"
-      uses: jmertic/slack-release-notifier@5221a45213eddc6206886797beb079e091c93406 # main
+      uses: jmertic/slack-release-notifier@9d7d3a84563d2ebc8f7b2271be6c9568fedd7f3a # main
 
     - name: 'Notify Slack #openexr'
       id: slack2
@@ -34,5 +34,6 @@
         slack_bot_token: ${{ secrets.SLACK_BOT_TOKEN }}
         slack_channel: "#openexr"
         project_logo: "https://artwork.aswf.io/projects/openexr/icon/color/openexr-icon-color.png"
-      uses: jmertic/slack-release-notifier@5221a45213eddc6206886797beb079e091c93406 # main
+      uses: jmertic/slack-release-notifier@9d7d3a84563d2ebc8f7b2271be6c9568fedd7f3a # main
+
       
\ No newline at end of file
diff -Nru openexr-3.4.6+ds/.github/workflows/release-sign.yml openexr-3.4.14/.github/workflows/release-sign.yml
--- openexr-3.4.6+ds/.github/workflows/release-sign.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/release-sign.yml	2026-08-05 04:29:55.000000000 +0300
@@ -14,12 +14,20 @@
 #   % pip install sigstore
 #   % sigstore verify github --cert-identity https://github.com/AcademySoftwareFoundation/openexr/.github/workflows/release-sign.yml@refs/tags/<tag> openexr-<tag>.tar.gz
 #
+# To sign an existing release after publish (e.g. workflow fix): Actions →
+# Sign Release → Run workflow → enter the release tag (e.g. v3.2.8).
 
 name: Sign Release
 
 on:
   release:
     types: [published]
+  workflow_dispatch:
+    inputs:
+      tag:
+        description: "Release tag to sign (e.g. v3.2.8)"
+        required: true
+        type: string
 
 permissions:
   contents: read
@@ -30,12 +38,10 @@
     runs-on: ubuntu-latest
 
     env:
-       TAG: ${{ github.ref_name }}
+      TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
     permissions:
-      contents: write
-      id-token: write
-      repository-projects: write
-       
+      contents: write   # required for gh release upload (attach assets)
+      id-token: write   # required for sigstore OIDC signing
     steps:
 
       - name: Set Prefix
@@ -49,13 +55,15 @@
         shell: bash
 
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+        with:
+          ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
 
       - name: Create archive
         run: git archive --format=tar.gz -o ${OPENEXR_TARBALL} --prefix ${OPENEXR_PREFIX} ${TAG}
 
       - name: Sign archive with Sigstore
-        uses: sigstore/gh-action-sigstore-python@a5caf349bc536fbef3668a10ed7f5cd309a4b53d # v3.2.0
+        uses: sigstore/gh-action-sigstore-python@790bc6befb9d733738f18d8f895854b453640ec9 # v3.5.0
         with:
           inputs: ${{ env.OPENEXR_TARBALL }}
           upload-signing-artifacts: false
diff -Nru openexr-3.4.6+ds/.github/workflows/scorecard.yml openexr-3.4.14/.github/workflows/scorecard.yml
--- openexr-3.4.6+ds/.github/workflows/scorecard.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/scorecard.yml	2026-08-05 04:29:55.000000000 +0300
@@ -28,12 +28,12 @@
 
     steps:
       - name: "Checkout code"
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
         with:
           persist-credentials: false
 
       - name: "Run analysis"
-        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
+        uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
         with:
           results_file: results.sarif
           results_format: sarif
@@ -46,7 +46,7 @@
 
       # Upload the results as artifacts (optional)
       - name: "Upload artifact"
-        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
           name: SARIF file
           path: results.sarif
@@ -54,6 +54,6 @@
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
+        uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
         with:
           sarif_file: results.sarif
diff -Nru openexr-3.4.6+ds/.github/workflows/snyk-scan-cron.yml openexr-3.4.14/.github/workflows/snyk-scan-cron.yml
--- openexr-3.4.6+ds/.github/workflows/snyk-scan-cron.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/snyk-scan-cron.yml	2026-08-05 04:29:55.000000000 +0300
@@ -17,7 +17,7 @@
     runs-on: ubuntu-latest
     if: github.repository == 'AcademySoftwareFoundation/openexr'
     steps:
-      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
 
       - uses: snyk/actions/setup@9adf32b1121593767fc3c057af55b55db032dc04 # v1.0.0
         id: snyk
diff -Nru openexr-3.4.6+ds/.github/workflows/website_preview_link.yml openexr-3.4.14/.github/workflows/website_preview_link.yml
--- openexr-3.4.6+ds/.github/workflows/website_preview_link.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/website_preview_link.yml	1970-01-01 02:00:00.000000000 +0200
@@ -1,35 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright (c) Contributors to the OpenEXR Project.
-#
-# GitHub Actions workflow file
-# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
-
-#
-# This action adds a link to the PR description pointing to the
-# readthedocs build of the website, for PRs that modify the website.
-#
-# Note that the link is also available in the PR checks, but it gets
-# buried among the output of the checks and isn't obvious.
-#
-
-name: Website preview link
-on:
-  pull_request_target:
-    types:
-      - opened
-    paths:
-      - 'website/**'
-
-permissions:
-  pull-requests: write
-
-jobs:
-  pull-request-links:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: readthedocs/actions/preview@cc0920454cf03ca8a3fbd3cbaa2ce2e509e70636 # v1.2
-        with:
-          project-slug: "openexr"
-          message-template: "Website preview: {docs-pr-index-url}"
-
-      
diff -Nru openexr-3.4.6+ds/.github/workflows/website.yml openexr-3.4.14/.github/workflows/website.yml
--- openexr-3.4.6+ds/.github/workflows/website.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.github/workflows/website.yml	2026-08-05 04:29:55.000000000 +0300
@@ -54,7 +54,7 @@
       
     steps:
       - name: Checkout
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
       - name: Create build directory
         run: mkdir _build
       - name: Install doxygen
@@ -64,7 +64,7 @@
         run: |
           python3 -m venv _venv
           source _venv/*/activate
-          pip3 install -r ../website/requirements.txt
+          pip3 install --require-hashes -r ../website/requirements-lock.txt
           cmake .. -DBUILD_WEBSITE=ON -DCMAKE_VERBOSE_MAKEFILE=ON -DSPHINX_OPTS=""
         working-directory: _build
         shell: bash
diff -Nru openexr-3.4.6+ds/.gitignore openexr-3.4.14/.gitignore
--- openexr-3.4.6+ds/.gitignore	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.gitignore	2026-08-05 04:29:55.000000000 +0300
@@ -64,3 +64,4 @@
 # Ignore Bazel generated files
 bazel-* 
 MODULE.bazel.lock
+*.vsidx
diff -Nru openexr-3.4.6+ds/MODULE.bazel openexr-3.4.14/MODULE.bazel
--- openexr-3.4.6+ds/MODULE.bazel	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/MODULE.bazel	2026-08-05 04:29:55.000000000 +0300
@@ -6,9 +6,9 @@
     compatibility_level = 1,
 )
 
-bazel_dep(name = "bazel_skylib", version = "1.9.0")
+bazel_dep(name = "bazel_skylib", version = "1.9.2")
 bazel_dep(name = "imath", version = "3.2.2.bcr.1")
 bazel_dep(name = "libdeflate", version = "1.25")
 bazel_dep(name = "openjph", version = "0.26.3.bcr.1")
-bazel_dep(name = "platforms", version = "1.0.0")
-bazel_dep(name = "rules_cc", version = "0.2.17")
+bazel_dep(name = "platforms", version = "1.1.0")
+bazel_dep(name = "rules_cc", version = "0.2.22")
diff -Nru openexr-3.4.6+ds/pyproject.toml openexr-3.4.14/pyproject.toml
--- openexr-3.4.6+ds/pyproject.toml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/pyproject.toml	2026-08-05 04:29:55.000000000 +0300
@@ -2,7 +2,7 @@
 # Copyright (c) Contributors to the OpenEXR Project.
 
 [build-system]
-requires = ["scikit-build-core==0.11.6", "pybind11"]
+requires = ["scikit-build-core==0.12.2", "pybind11==2.13.6", "build==1.2.2.post1"]
 build-backend = "scikit_build_core.build"
 
 [project]
@@ -82,10 +82,13 @@
 test-skip = ["*universal2:arm64"]
 build-verbosity = 1
 
-manylinux-x86_64-image = "manylinux2014"
-manylinux-i686-image = "manylinux2014"
-manylinux-aarch64-image = "manylinux2014"
+manylinux-x86_64-image = "manylinux_2_28"
+manylinux-i686-image = "manylinux_2_28"
+manylinux-aarch64-image = "manylinux_2_28"
 
 # Needed for full C++17 support
 [tool.cibuildwheel.macos.environment]
 MACOSX_DEPLOYMENT_TARGET = "10.15"
+# pass compilers directly to avoid Homebrew LLVM libunwind
+CMAKE_ARGS = "-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++"
+
diff -Nru openexr-3.4.6+ds/README.md openexr-3.4.14/README.md
--- openexr-3.4.6+ds/README.md	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/README.md	2026-08-05 04:29:55.000000000 +0300
@@ -144,7 +144,10 @@
 
 * **Report a security vulnerability:**
 
-  - Email to security@openexr.com
+  - File a GitHub [security
+    advisory](https://github.com/AcademySoftwareFoundation/openexr/security/advisories/new).
+    Email security@openexr.com for private/secure discussion with the project
+    maintainers.
 
 * **Contribute a Fix, Feature, or Improvement:**
 
@@ -155,6 +158,9 @@
 
   - Submit a Pull Request: https://github.com/AcademySoftwareFoundation/openexr/pulls
 
+  - If you'd like to contribute and could use some ideas of what to
+    do, browse "good first issues" [here](https://github.com/AcademySoftwareFoundation/openexr/contribute) or on [Clotributor](https://clotributor.dev/search?ts_query_web=openexr&page=1).
+
 # Resources
 
 - Website: http://www.openexr.com
diff -Nru openexr-3.4.6+ds/.readthedocs.yml openexr-3.4.14/.readthedocs.yml
--- openexr-3.4.6+ds/.readthedocs.yml	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/.readthedocs.yml	2026-08-05 04:29:55.000000000 +0300
@@ -11,7 +11,8 @@
 build:
   os: "ubuntu-22.04"
   tools:
-    python: "3.9"
+    # urllib3>=2.7 (website/requirements.txt) requires Python >=3.10
+    python: "3.11"
        
 sphinx:
   configuration: website/conf.py
diff -Nru openexr-3.4.6+ds/SECURITY.md openexr-3.4.14/SECURITY.md
--- openexr-3.4.6+ds/SECURITY.md	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/SECURITY.md	2026-08-05 04:29:55.000000000 +0300
@@ -16,59 +16,91 @@
 within 48 hours. Our policy is to address critical security vulnerabilities
 rapidly and post patches within 14 days if possible.
 
+## CVE Assignment
+
+We request a CVE when an untrusted party can plausibly trigger the flaw
+through normal product inputs (for example, a crafted EXR or other data
+the application is meant to process); otherwise we treat it as a regular
+bugfix, document it in a GitHub security advisory or release notes when
+helpful, and do not request a CVE for API-only or caller-controlled
+failures with no realistic adversarial path.
+
+Flaws whose root cause lies in a bundled dependency (such as OpenJPH)
+are fixed upstream; the upstream project owns the CVE when one is
+warranted.  Note that this applies even when a flaw is detected in
+dependency code vendored into OpenEXR's `external/` source directory.
+OpenEXR addresses them by updating the dependency and noting the
+upstream advisory in release notes. We request a CVE for OpenEXR
+itself only when the flaw is in our code and untrusted input can reach
+it through normal use of the library.
+
 ## Known Vulnerabilities
 
-These vulnerabilities are present in the given versions:
+| CVE | Affected Versions | Patched Versions |
+| --- | ----------------- | ---------------- |
 
-* [CVE-2026-26981](https://www.cve.org/CVERecord?id=CVE-2025-64181) 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.3.6, 3.4.0, 3.4.1, 3.4.2, 3.4.3, 3.4.4
-* [CVE-2025-64181](https://www.cve.org/CVERecord?id=CVE-2025-64181) 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.4.0, 3.4.1, 3.4.2
-* [CVE-2025-64182](https://www.cve.org/CVERecord?id=CVE-2025-64182) 3.2.0, 3.2.1, 3.2.3, 3.2.4, 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5,
-  3.4.0, 3.4.1, 3.4.2
-* [CVE-2025-64183](https://www.cve.org/CVERecord?id=CVE-2025-64183) 3.2.0, 3.2.1, 3.2.3, 3.2.4, 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5,
-  3.4.0, 3.4.1, 3.4.2
-* [CVE-2025-48074](https://www.cve.org/CVERecord?id=CVE-2025-48074) 3.3.2
-* [CVE-2025-48073](https://www.cve.org/CVERecord?id=CVE-2025-48073) 3.3.2
-* [CVE-2025-48072](https://www.cve.org/CVERecord?id=CVE-2025-48072) 3.3.2
-* [CVE-2025-48071](https://www.cve.org/CVERecord?id=CVE-2025-48071) 3.3.2 3.3.1 3.3.0
-* [CVE-2025-12840](https://www.cve.org/CVERecord?id=CVE-2025-12840) 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.4.0, 3.4.1, 3.4.2
-* [CVE-2025-12839](https://www.cve.org/CVERecord?id=CVE-2025-12839) 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.4.0, 3.4.1, 3.4.2
-* [CVE-2025-12495](https://www.cve.org/CVERecord?id=CVE-2025-12495) 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.4.0, 3.4.1, 3.4.2
-* [CVE-2023-5841](https://nvd.nist.gov/vuln/detail/CVE-2023-5841) 3.1.0, 3.1.1, 3.1.2, 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.1.10 3.1.11 3.2.0 3.2.1
-* [CVE-2021-45942](https://nvd.nist.gov/vuln/detail/CVE-2021-45942) 3.1.0, 3.1.1, 3.1.2, 3.1.3
-* [CVE-2021-20296](https://nvd.nist.gov/vuln/detail/CVE-2021-20296) 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2, 2.5.3
-* [CVE-2021-3479](https://nvd.nist.gov/vuln/detail/CVE-2021-3479) 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2, 2.5.3
-* [CVE-2021-3478](https://nvd.nist.gov/vuln/detail/CVE-2021-3478) 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2, 2.5.3
-* [CVE-2021-3477](https://nvd.nist.gov/vuln/detail/CVE-2021-3477) 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2, 2.5.3
-* [CVE-2021-3476](https://nvd.nist.gov/vuln/detail/CVE-2021-3476) 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2, 2.5.3
-* [CVE-2021-3475](https://nvd.nist.gov/vuln/detail/CVE-2021-3475) 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2, 2.5.3
-* [CVE-2021-3474](https://nvd.nist.gov/vuln/detail/CVE-2021-3474) 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1, 2.5.2, 2.5.3
-* [CVE-2020-16589](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-16589) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0
-* [CVE-2020-16588](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-16588) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0
-* [CVE-2020-16587](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-16587) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0
-* [CVE-2020-15306](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15306) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0, 2.4.1, 2.5.0, 2.5.1
-* [CVE-2020-15305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15305) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0, 2.4.1, 2.5.0, 2.5.1
-* [CVE-2020-15304](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15304) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0, 2.4.1, 2.5.0, 2.5.1
-* [CVE-2020-11765](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11765) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2020-11764](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11764) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2020-11763](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11763) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2020-11762](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11762) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2020-11761](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11761) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2020-11760](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11760) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2020-11759](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11759) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2020-11758](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11758) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0, 2.4.0
-* [CVE-2018-18444](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-18444) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0
-* [CVE-2018-18443](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-18443) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0
-* [CVE-2017-9116](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9116) 2.0.0, 2.0.1, 2.1.0, 2.2.0
-* [CVE-2017-9115](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9115) 2.0.0, 2.0.1, 2.1.0, 2.2.0
-* [CVE-2017-9114](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9114) 2.0.0, 2.0.1, 2.1.0, 2.2.0
-* [CVE-2017-9113](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9113) 2.0.0, 2.0.1, 2.1.0, 2.2.0
-* [CVE-2017-9112](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9112) 2.0.0, 2.0.1, 2.1.0, 2.2.0
-* [CVE-2017-9111](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9111) 2.0.0, 2.0.1, 2.1.0, 2.2.0
-* [CVE-2017-9110](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9110) 2.0.0, 2.0.1, 2.1.0, 2.2.0
-* [CVE-2017-12596](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12596) 2.0.0, 2.0.1, 2.1.0, 2.2.0, 2.2.1, 2.3.0
-* [CVE-2009-1722](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1722) 1.0.*, 1.1.*, 1.2.*, 1.3.*, 1.4.*, 1.5.*, 1.6.*
-* [CVE-2009-1721](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1721) 1.0.*, 1.1.*, 1.2.*, 1.3.*, 1.4.*, 1.5.*, 1.6.*
-* [CVE-2009-1720](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1720) 1.0.*, 1.1.*, 1.2.*, 1.3.*, 1.4.*, 1.5.*, 1.6.*
+| [CVE-2026-42217](https://www.cve.org/CVERecord?id=CVE-2026-42217) | 3.2.0–3.2.8, 3.3.0–3.3.10, 3.4.0–3.4.10 | 3.2.9, 3.3.11, 3.4.11 |
+| [CVE-2026-42216](https://www.cve.org/CVERecord?id=CVE-2026-42216) | 3.2.0–3.2.8, 3.3.0–3.3.10, 3.4.0–3.4.10 | 3.2.9, 3.3.11, 3.4.11 |
+| [CVE-2026-41142](https://www.cve.org/CVERecord?id=CVE-2026-41142) | 3.2.0–3.2.8, 3.3.0–3.3.10, 3.4.0–3.4.10 | 3.2.9, 3.3.11, 3.4.11 |
+| [CVE-2026-40250](https://www.cve.org/CVERecord?id=CVE-2026-40250) | 3.2.0–3.2.7, 3.3.0–3.3.9, 3.4.0–3.4.9 | 3.2.8, 3.3.10, 3.4.10 |
+| [CVE-2026-40244](https://www.cve.org/CVERecord?id=CVE-2026-40244) | 3.2.0–3.2.7, 3.3.0–3.3.9, 3.4.0–3.4.9 | 3.2.8, 3.3.10, 3.4.10 |
+| [CVE-2026-39886](https://www.cve.org/CVERecord?id=CVE-2026-39886) | 3.4.0–3.4.9 | 3.4.10 |
+| [CVE-2026-34589](https://www.cve.org/CVERecord?id=CVE-2026-34589) | 3.2.0–3.2.6, 3.3.0–3.3.8, 3.4.0–3.4.8 | 3.2.7, 3.3.9, 3.4.9 |
+| [CVE-2026-34588](https://www.cve.org/CVERecord?id=CVE-2026-34588) | 3.1.x, 3.2.0–3.2.6, 3.3.0–3.3.8, 3.4.0–3.4.8 | 3.2.7, 3.3.9, 3.4.9 |
+| [CVE-2026-34545](https://www.cve.org/CVERecord?id=CVE-2026-34545) | 3.4.0–3.4.6 | 3.4.7 |
+| [CVE-2026-34544](https://www.cve.org/CVERecord?id=CVE-2026-34544) | 3.2.0–3.2.6, 3.3.0–3.3.8, 3.4.0–3.4.7 | 3.2.7, 3.3.9, 3.4.8 |
+| [CVE-2026-34543](https://www.cve.org/CVERecord?id=CVE-2026-34543) | 3.2.0–3.2.6, 3.3.0–3.3.8, 3.4.0–3.4.7 | 3.2.7, 3.3.9, 3.4.8 |
+| [CVE-2026-34380](https://www.cve.org/CVERecord?id=CVE-2026-34380) | 3.2.0–3.2.6, 3.3.0–3.3.8, 3.4.0–3.4.8 | 3.2.7, 3.3.9, 3.4.9 |
+| [CVE-2026-34379](https://www.cve.org/CVERecord?id=CVE-2026-34379) | 3.2.0–3.2.6, 3.3.0–3.3.8, 3.4.0–3.4.8 | 3.2.7, 3.3.9, 3.4.9 |
+| [CVE-2026-34378](https://www.cve.org/CVERecord?id=CVE-2026-34378) | 3.4.0–3.4.8 | 3.4.9 |
+| [CVE-2026-27622](https://www.cve.org/CVERecord?id=CVE-2026-27622) | 2.x, 3.0.x, 3.1.x, 3.2.0–3.2.5, 3.3.0–3.3.7, 3.4.0–3.4.5 | 3.2.6, 3.3.8, 3.4.6 |
+| [CVE-2026-26981](https://www.cve.org/CVERecord?id=CVE-2026-26981) | 3.3.0–3.3.6, 3.4.0–3.4.4 | 3.3.7, 3.4.5 |
+| [CVE-2025-64183](https://www.cve.org/CVERecord?id=CVE-2025-64183) | 3.2.0–3.2.4, 3.3.0–3.3.5, 3.4.0–3.4.2 | 3.2.5, 3.3.6, 3.4.3 |
+| [CVE-2025-64182](https://www.cve.org/CVERecord?id=CVE-2025-64182) | 3.2.0–3.2.4, 3.3.0–3.3.5, 3.4.0–3.4.2 | 3.2.5, 3.3.6, 3.4.3 |
+| [CVE-2025-64181](https://www.cve.org/CVERecord?id=CVE-2025-64181) | 3.3.0–3.3.5, 3.4.0–3.4.2 | 3.3.6, 3.4.3 |
+| [CVE-2025-48074](https://www.cve.org/CVERecord?id=CVE-2025-48074) | 3.3.2 | 3.3.3 |
+| [CVE-2025-48073](https://www.cve.org/CVERecord?id=CVE-2025-48073) | 3.3.2 | 3.3.3 |
+| [CVE-2025-48072](https://www.cve.org/CVERecord?id=CVE-2025-48072) | 3.3.2 | 3.3.3 |
+| [CVE-2025-48071](https://www.cve.org/CVERecord?id=CVE-2025-48071) | 3.3.0–3.3.2 | 3.3.3 |
+| [CVE-2025-12840](https://www.cve.org/CVERecord?id=CVE-2025-12840) | 3.3.0–3.3.5, 3.4.0–3.4.2 | 3.3.6, 3.4.3 |
+| [CVE-2025-12839](https://www.cve.org/CVERecord?id=CVE-2025-12839) | 3.3.0–3.3.5, 3.4.0–3.4.2 | 3.3.6, 3.4.3 |
+| [CVE-2025-12495](https://www.cve.org/CVERecord?id=CVE-2025-12495) | 3.3.0–3.3.5, 3.4.0–3.4.2 | 3.3.6, 3.4.3 |
+| [CVE-2023-5841](https://nvd.nist.gov/vuln/detail/CVE-2023-5841) | 3.1.0–3.1.11, 3.2.0–3.2.1 | 3.2.2 |
+| [CVE-2021-45942](https://nvd.nist.gov/vuln/detail/CVE-2021-45942) | 3.1.0–3.1.3 | 3.1.4 |
+| [CVE-2021-20296](https://nvd.nist.gov/vuln/detail/CVE-2021-20296) | 2.4.0–2.4.2, 2.5.0–2.5.3 | 2.4.3, 2.5.4 |
+| [CVE-2021-3479](https://nvd.nist.gov/vuln/detail/CVE-2021-3479) | 2.4.0–2.4.2, 2.5.0–2.5.3 | 2.4.3, 2.5.4 |
+| [CVE-2021-3478](https://nvd.nist.gov/vuln/detail/CVE-2021-3478) | 2.4.0–2.4.2, 2.5.0–2.5.3 | 2.4.3, 2.5.4 |
+| [CVE-2021-3477](https://nvd.nist.gov/vuln/detail/CVE-2021-3477) | 2.4.0–2.4.2, 2.5.0–2.5.3 | 2.4.3, 2.5.4 |
+| [CVE-2021-3476](https://nvd.nist.gov/vuln/detail/CVE-2021-3476) | 2.4.0–2.4.2, 2.5.0–2.5.3 | 2.4.3, 2.5.4 |
+| [CVE-2021-3475](https://nvd.nist.gov/vuln/detail/CVE-2021-3475) | 2.4.0–2.4.2, 2.5.0–2.5.3 | 2.4.3, 2.5.4 |
+| [CVE-2021-3474](https://nvd.nist.gov/vuln/detail/CVE-2021-3474) | 2.4.0–2.4.2, 2.5.0–2.5.3 | 2.4.3, 2.5.4 |
+| [CVE-2020-16589](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-16589) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0 | 2.4.0 |
+| [CVE-2020-16588](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-16588) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0 | 2.4.0 |
+| [CVE-2020-16587](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-16587) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0 | 2.4.0 |
+| [CVE-2020-15306](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15306) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0–2.4.1, 2.5.0–2.5.1 | 2.5.2 |
+| [CVE-2020-15305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15305) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0–2.4.1, 2.5.0–2.5.1 | 2.5.2 |
+| [CVE-2020-15304](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15304) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0–2.4.1, 2.5.0–2.5.1 | 2.5.2 |
+| [CVE-2020-11765](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11765) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2020-11764](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11764) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2020-11763](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11763) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2020-11762](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11762) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2020-11761](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11761) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2020-11760](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11760) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2020-11759](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11759) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2020-11758](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11758) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0, 2.4.0 | 2.4.1 |
+| [CVE-2018-18444](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-18444) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0 | 2.4.0 |
+| [CVE-2018-18443](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-18443) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1, 2.3.0 | 2.4.0 |
+| [CVE-2017-9116](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9116) | 2.0.0–2.0.1, 2.1.0, 2.2.0 | 2.2.1 |
+| [CVE-2017-9115](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9115) | 2.0.0–2.0.1, 2.1.0, 2.2.0 | 2.2.1 |
+| [CVE-2017-9114](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9114) | 2.0.0–2.0.1, 2.1.0, 2.2.0 | 2.2.1 |
+| [CVE-2017-9113](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9113) | 2.0.0–2.0.1, 2.1.0, 2.2.0 | 2.2.1 |
+| [CVE-2017-9112](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9112) | 2.0.0–2.0.1, 2.1.0, 2.2.0 | 2.2.1 |
+| [CVE-2017-9111](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9111) | 2.0.0–2.0.1, 2.1.0, 2.2.0 | 2.2.1 |
+| [CVE-2017-9110](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9110) | 2.0.0–2.0.1, 2.1.0, 2.2.0 | 2.2.1 |
+| [CVE-2017-12596](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12596) | 2.0.0–2.0.1, 2.1.0, 2.2.0–2.2.1 | 2.3.0 |
+| [CVE-2009-1722](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1722) | 1.0.x, 1.1.x, 1.2.x, 1.3.x, 1.4.x, 1.5.x, 1.6.x | 1.7.0 |
+| [CVE-2009-1721](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1721) | 1.0.x, 1.1.x, 1.2.x, 1.3.x, 1.4.x, 1.5.x, 1.6.x | 1.7.0 |
+| [CVE-2009-1720](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1720) | 1.0.x, 1.1.x, 1.2.x, 1.3.x, 1.4.x, 1.5.x, 1.6.x | 1.7.0 |
 
 See the [release notes](CHANGES.md) for more information.
 
@@ -80,6 +112,7 @@
 | Version / branch  | Supported                                            |
 |-------| ---------------------------------------------------- |
 | main  | :white_check_mark: :construction: All fixes immediately, although this branch is under development with potential unstable ABI/API
+| 3.4.x | :white_check_mark: All fixes that can be backported without breaking ABI compatibility
 | 3.3.x | :white_check_mark: All fixes that can be backported without breaking ABI compatibility
 | 3.2.x | :warning: Patch releases considered in response to specific requests
 | 3.1.x | :warning: Patch releases considered in response to specific requests
@@ -161,6 +194,32 @@
 overflows, out-of-memory faults, or segmentation faults that could be
 exploitable as denial-of-service attacks.
 
+### Image Size Limits and Out-of-Memory Failures
+
+The OpenEXR file format places no fixed limit on image size, except
+that image width and height are represented by signed 32-bit integers
+and therefore technically limited to a maximum of 2,147,483,647.
+
+Memory allocation failures caused by large image dimensions declared
+in file headers are not considered security vulnerabilities when the
+allocation size is proportional to the declared image dimensions. EXR
+files can legitimately describe very large images, and the memory
+required to decode them is inherently proportional to their pixel
+count. Exhausting available memory on a given machine is a system
+resource constraint, not a library defect — the same file that
+triggers an out-of-memory error on one machine may load successfully
+on another with more memory.
+
+The OpenEXR library provides 
+`Imf::Header::setMaxImageSize(int maxWidth,int maxHeight)` and
+`Imf::Header:"setMaxTileSize(int maxWidth,int maxHeight)` (and
+`exr_set_default_maximum_image_size()` and
+`exr_set_default_maximum_tile_size()` in OpenEXRCore) to allow
+applications to reject files with dimensions exceeding a configurable
+limit before any large allocation occurs. Applications processing
+untrusted EXR files should set these limits to values appropriate for
+their deployment environment.
+
 ### Hardening
 
 #### Testing
diff -Nru openexr-3.4.6+ds/share/ci/install_manifest/install_manifest.linux.13.txt openexr-3.4.14/share/ci/install_manifest/install_manifest.linux.13.txt
--- openexr-3.4.6+ds/share/ci/install_manifest/install_manifest.linux.13.txt	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/ci/install_manifest/install_manifest.linux.13.txt	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,206 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) Contributors to the OpenEXR Project.
+# cmake -B /__w/openexr/openexr/_build -S /__w/openexr/openexr -DCMAKE_INSTALL_PREFIX=/__w/openexr/openexr/_install -DCMAKE_BUILD_TYPE=Release -DOPENEXR_CXX_STANDARD=17 -DBUILD_SHARED_LIBS=OFF -DOPENEXR_ENABLE_THREADING=ON -DOPENEXR_INSTALL_PKG_CONFIG=ON -DOPENEXR_INSTALL_DOCS=ON -DOPENEXR_BUILD_EXAMPLES=ON -DOPENEXR_BUILD_TOOLS=ON -DOPENEXR_FORCE_INTERNAL_IMATH=OFF -DOPENEXR_FORCE_INTERNAL_DEFLATE=OFF -DOPENEXR_FORCE_INTERNAL_OPENJPH=ON -DBUILD_TESTING=ON -DOPENEXR_RUN_FUZZ_TESTS=OFF -DCMAKE_VERBOSE_MAKEFILE=ON 
+bin/exr2aces
+bin/exrenvmap
+bin/exrheader
+bin/exrinfo
+bin/exrmakepreview
+bin/exrmaketiled
+bin/exrmanifest
+bin/exrmetrics
+bin/exrmultipart
+bin/exrmultiview
+bin/exrstdattr
+include/OpenEXR/Iex.h
+include/OpenEXR/IexBaseExc.h
+include/OpenEXR/IexConfig.h
+include/OpenEXR/IexErrnoExc.h
+include/OpenEXR/IexExport.h
+include/OpenEXR/IexForward.h
+include/OpenEXR/IexMacros.h
+include/OpenEXR/IexMathExc.h
+include/OpenEXR/IexMathFloatExc.h
+include/OpenEXR/IexMathIeeeExc.h
+include/OpenEXR/IexNamespace.h
+include/OpenEXR/IexThrowErrnoExc.h
+include/OpenEXR/IlmThread.h
+include/OpenEXR/IlmThreadConfig.h
+include/OpenEXR/IlmThreadExport.h
+include/OpenEXR/IlmThreadForward.h
+include/OpenEXR/IlmThreadMutex.h
+include/OpenEXR/IlmThreadNamespace.h
+include/OpenEXR/IlmThreadPool.h
+include/OpenEXR/IlmThreadProcessGroup.h
+include/OpenEXR/IlmThreadSemaphore.h
+include/OpenEXR/ImfAcesFile.h
+include/OpenEXR/ImfArray.h
+include/OpenEXR/ImfAttribute.h
+include/OpenEXR/ImfBoxAttribute.h
+include/OpenEXR/ImfBytesAttribute.h
+include/OpenEXR/ImfCRgbaFile.h
+include/OpenEXR/ImfChannelList.h
+include/OpenEXR/ImfChannelListAttribute.h
+include/OpenEXR/ImfCheckFile.h
+include/OpenEXR/ImfChromaticities.h
+include/OpenEXR/ImfChromaticitiesAttribute.h
+include/OpenEXR/ImfCompositeDeepScanLine.h
+include/OpenEXR/ImfCompression.h
+include/OpenEXR/ImfCompressionAttribute.h
+include/OpenEXR/ImfCompressor.h
+include/OpenEXR/ImfContext.h
+include/OpenEXR/ImfContextInit.h
+include/OpenEXR/ImfConvert.h
+include/OpenEXR/ImfDeepCompositing.h
+include/OpenEXR/ImfDeepFrameBuffer.h
+include/OpenEXR/ImfDeepImage.h
+include/OpenEXR/ImfDeepImageChannel.h
+include/OpenEXR/ImfDeepImageIO.h
+include/OpenEXR/ImfDeepImageLevel.h
+include/OpenEXR/ImfDeepImageState.h
+include/OpenEXR/ImfDeepImageStateAttribute.h
+include/OpenEXR/ImfDeepScanLineInputFile.h
+include/OpenEXR/ImfDeepScanLineInputPart.h
+include/OpenEXR/ImfDeepScanLineOutputFile.h
+include/OpenEXR/ImfDeepScanLineOutputPart.h
+include/OpenEXR/ImfDeepTiledInputFile.h
+include/OpenEXR/ImfDeepTiledInputPart.h
+include/OpenEXR/ImfDeepTiledOutputFile.h
+include/OpenEXR/ImfDeepTiledOutputPart.h
+include/OpenEXR/ImfDoubleAttribute.h
+include/OpenEXR/ImfEnvmap.h
+include/OpenEXR/ImfEnvmapAttribute.h
+include/OpenEXR/ImfExport.h
+include/OpenEXR/ImfFlatImage.h
+include/OpenEXR/ImfFlatImageChannel.h
+include/OpenEXR/ImfFlatImageIO.h
+include/OpenEXR/ImfFlatImageLevel.h
+include/OpenEXR/ImfFloatAttribute.h
+include/OpenEXR/ImfFloatVectorAttribute.h
+include/OpenEXR/ImfForward.h
+include/OpenEXR/ImfFrameBuffer.h
+include/OpenEXR/ImfFramesPerSecond.h
+include/OpenEXR/ImfGenericInputFile.h
+include/OpenEXR/ImfGenericOutputFile.h
+include/OpenEXR/ImfHTCompressor.h
+include/OpenEXR/ImfHeader.h
+include/OpenEXR/ImfHuf.h
+include/OpenEXR/ImfIDManifest.h
+include/OpenEXR/ImfIDManifestAttribute.h
+include/OpenEXR/ImfIO.h
+include/OpenEXR/ImfImage.h
+include/OpenEXR/ImfImageChannel.h
+include/OpenEXR/ImfImageChannelRenaming.h
+include/OpenEXR/ImfImageDataWindow.h
+include/OpenEXR/ImfImageIO.h
+include/OpenEXR/ImfImageLevel.h
+include/OpenEXR/ImfInputFile.h
+include/OpenEXR/ImfInputPart.h
+include/OpenEXR/ImfInt64.h
+include/OpenEXR/ImfIntAttribute.h
+include/OpenEXR/ImfKeyCode.h
+include/OpenEXR/ImfKeyCodeAttribute.h
+include/OpenEXR/ImfLineOrder.h
+include/OpenEXR/ImfLineOrderAttribute.h
+include/OpenEXR/ImfLut.h
+include/OpenEXR/ImfMatrixAttribute.h
+include/OpenEXR/ImfMisc.h
+include/OpenEXR/ImfMultiPartInputFile.h
+include/OpenEXR/ImfMultiPartOutputFile.h
+include/OpenEXR/ImfMultiView.h
+include/OpenEXR/ImfName.h
+include/OpenEXR/ImfNamespace.h
+include/OpenEXR/ImfOpaqueAttribute.h
+include/OpenEXR/ImfOutputFile.h
+include/OpenEXR/ImfOutputPart.h
+include/OpenEXR/ImfPartHelper.h
+include/OpenEXR/ImfPartType.h
+include/OpenEXR/ImfPixelType.h
+include/OpenEXR/ImfPreviewImage.h
+include/OpenEXR/ImfPreviewImageAttribute.h
+include/OpenEXR/ImfRational.h
+include/OpenEXR/ImfRationalAttribute.h
+include/OpenEXR/ImfRgba.h
+include/OpenEXR/ImfRgbaFile.h
+include/OpenEXR/ImfRgbaYca.h
+include/OpenEXR/ImfSampleCountChannel.h
+include/OpenEXR/ImfStandardAttributes.h
+include/OpenEXR/ImfStdIO.h
+include/OpenEXR/ImfStringAttribute.h
+include/OpenEXR/ImfStringVectorAttribute.h
+include/OpenEXR/ImfTestFile.h
+include/OpenEXR/ImfThreading.h
+include/OpenEXR/ImfTileDescription.h
+include/OpenEXR/ImfTileDescriptionAttribute.h
+include/OpenEXR/ImfTiledInputFile.h
+include/OpenEXR/ImfTiledInputPart.h
+include/OpenEXR/ImfTiledOutputFile.h
+include/OpenEXR/ImfTiledOutputPart.h
+include/OpenEXR/ImfTiledRgbaFile.h
+include/OpenEXR/ImfTimeCode.h
+include/OpenEXR/ImfTimeCodeAttribute.h
+include/OpenEXR/ImfUtilExport.h
+include/OpenEXR/ImfVecAttribute.h
+include/OpenEXR/ImfVersion.h
+include/OpenEXR/ImfWav.h
+include/OpenEXR/ImfXdr.h
+include/OpenEXR/OpenEXRConfig.h
+include/OpenEXR/openexr.h
+include/OpenEXR/openexr_attr.h
+include/OpenEXR/openexr_base.h
+include/OpenEXR/openexr_chunkio.h
+include/OpenEXR/openexr_coding.h
+include/OpenEXR/openexr_compression.h
+include/OpenEXR/openexr_config.h
+include/OpenEXR/openexr_context.h
+include/OpenEXR/openexr_debug.h
+include/OpenEXR/openexr_decode.h
+include/OpenEXR/openexr_encode.h
+include/OpenEXR/openexr_errors.h
+include/OpenEXR/openexr_part.h
+include/OpenEXR/openexr_std_attr.h
+include/OpenEXR/openexr_version.h
+lib64/cmake/OpenEXR/OpenEXRConfig.cmake
+lib64/cmake/OpenEXR/OpenEXRConfigVersion.cmake
+lib64/cmake/OpenEXR/OpenEXRTargets-release.cmake
+lib64/cmake/OpenEXR/OpenEXRTargets.cmake
+lib64/libIex-${OPENEXR_VERSION_MAJOR}_${OPENEXR_VERSION_MINOR}.a
+lib64/libIlmThread-${OPENEXR_VERSION_MAJOR}_${OPENEXR_VERSION_MINOR}.a
+lib64/libOpenEXR-${OPENEXR_VERSION_MAJOR}_${OPENEXR_VERSION_MINOR}.a
+lib64/libOpenEXRCore-${OPENEXR_VERSION_MAJOR}_${OPENEXR_VERSION_MINOR}.a
+lib64/libOpenEXRUtil-${OPENEXR_VERSION_MAJOR}_${OPENEXR_VERSION_MINOR}.a
+lib64/pkgconfig/OpenEXR.pc
+share/doc/OpenEXR/examples/deepExamples.cpp
+share/doc/OpenEXR/examples/deepExamples.h
+share/doc/OpenEXR/examples/deepTiledExamples.cpp
+share/doc/OpenEXR/examples/deepTiledExamples.h
+share/doc/OpenEXR/examples/drawImage.cpp
+share/doc/OpenEXR/examples/drawImage.h
+share/doc/OpenEXR/examples/generalInterfaceExamples.cpp
+share/doc/OpenEXR/examples/generalInterfaceExamples.h
+share/doc/OpenEXR/examples/generalInterfaceTiledExamples.cpp
+share/doc/OpenEXR/examples/generalInterfaceTiledExamples.h
+share/doc/OpenEXR/examples/lowLevelIoExamples.cpp
+share/doc/OpenEXR/examples/lowLevelIoExamples.h
+share/doc/OpenEXR/examples/main.cpp
+share/doc/OpenEXR/examples/multipartExamples.cpp
+share/doc/OpenEXR/examples/multipartExamples.h
+share/doc/OpenEXR/examples/namespaceAlias.h
+share/doc/OpenEXR/examples/previewImageExamples.cpp
+share/doc/OpenEXR/examples/previewImageExamples.h
+share/doc/OpenEXR/examples/rgbaInterfaceExamples.cpp
+share/doc/OpenEXR/examples/rgbaInterfaceExamples.h
+share/doc/OpenEXR/examples/rgbaInterfaceTiledExamples.cpp
+share/doc/OpenEXR/examples/rgbaInterfaceTiledExamples.h
+share/man/man1/exr2aces.1
+share/man/man1/exrcheck.1
+share/man/man1/exrenvmap.1
+share/man/man1/exrheader.1
+share/man/man1/exrinfo.1
+share/man/man1/exrmakepreview.1
+share/man/man1/exrmaketiled.1
+share/man/man1/exrmanifest.1
+share/man/man1/exrmetrics.1
+share/man/man1/exrmultipart.1
+share/man/man1/exrmultiview.1
+share/man/man1/exrstdattr.1
diff -Nru openexr-3.4.6+ds/share/ci/scripts/install_imath.sh openexr-3.4.14/share/ci/scripts/install_imath.sh
--- openexr-3.4.6+ds/share/ci/scripts/install_imath.sh	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/share/ci/scripts/install_imath.sh	2026-08-05 04:29:55.000000000 +0300
@@ -15,12 +15,20 @@
 # The sudo is nececessary since the installation goes to /usr/local.
 SUDO=$(command -v sudo >/dev/null 2>&1 && echo sudo || echo "")
 
+# In an MSYS2 mingw shell, install into $MINGW_PREFIX (e.g. /mingw32)
+# instead of the default /usr/local, so find_package() locates it the
+# same way it would find a pacboy-installed package.
+PREFIX_ARG=()
+if [ -n "$MINGW_PREFIX" ]; then
+  PREFIX_ARG=("-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX")
+fi
+
 git clone https://github.com/AcademySoftwareFoundation/Imath.git
 cd Imath
 
 git checkout ${TAG}
 
-cmake -S . -B _build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF
+cmake -S . -B _build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF "${PREFIX_ARG[@]}"
 $SUDO cmake --build _build \
       --target install \
       --config Release \
diff -Nru openexr-3.4.6+ds/share/ci/scripts/install_openjph.sh openexr-3.4.14/share/ci/scripts/install_openjph.sh
--- openexr-3.4.6+ds/share/ci/scripts/install_openjph.sh	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/share/ci/scripts/install_openjph.sh	2026-08-05 04:29:55.000000000 +0300
@@ -15,13 +15,21 @@
 # The sudo is nececessary since the installation goes to /usr/local.
 SUDO=$(command -v sudo >/dev/null 2>&1 && echo sudo || echo "")
 
+# In an MSYS2 mingw shell, install into $MINGW_PREFIX (e.g. /mingw32)
+# instead of the default /usr/local, so find_package()/pkg_check_modules()
+# locates it the same way it would find a pacboy-installed package.
+PREFIX_ARG=()
+if [ -n "$MINGW_PREFIX" ]; then
+  PREFIX_ARG=("-DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX")
+fi
+
 git clone https://github.com/aous72/OpenJPH.git
 cd OpenJPH
 
 git checkout ${TAG}
 
 cd build
-cmake -DOJPH_ENABLE_TIFF_SUPPORT=OFF -DCMAKE_BUILD_TYPE=Release .. 
+cmake -DOJPH_ENABLE_TIFF_SUPPORT=OFF -DCMAKE_BUILD_TYPE=Release "${PREFIX_ARG[@]}" ..
 $SUDO cmake --build . \
       --target install \
       --config Release \
diff -Nru openexr-3.4.6+ds/share/util/release/candidate.py openexr-3.4.14/share/util/release/candidate.py
--- openexr-3.4.6+ds/share/util/release/candidate.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/candidate.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Format a staged-release announcement message and print to stdout."""
+
+from __future__ import annotations
+
+import sys
+from subprocess import PIPE, run
+
+from _common import format_weekday_month_day, load_release_notes, require_repo_url
+
+
+def markdown_to_html(markdown_text: str) -> str:
+    result = run(
+        ["pandoc", "-f", "markdown", "-t", "html"],
+        input=markdown_text,
+        text=True,
+        capture_output=True,
+        check=True,
+    )
+    return (
+        "<style> body { line-height: 1.0; margin: 0; padding: 0; } "
+        "p, h1, h2, h3, h4, h5, h6 { margin: 0; padding: 0; } </style> "
+        f"{result.stdout}"
+    )
+
+
+def main() -> None:
+    if len(sys.argv) < 2:
+        print("Usage: python share/util/release/candidate.py <tag> e.g. candidate.py v3.4.7-rc", file=sys.stderr)
+        sys.exit(1)
+    tag = sys.argv[1]
+    release_date, release_notes, release_version = load_release_notes(tag)
+    html_notes = markdown_to_html(release_notes)
+    date_string = format_weekday_month_day(release_date)
+    url = require_repo_url()
+    project = url.split("/")[-1]
+    if project == "openexr":
+        project = "OpenEXR"
+    print(
+        f"{project} {release_version} is staged for release at tag "
+        f'<a href="{url}/releases/tag/{tag}">{tag}</a> and will be released '
+        f"officially {date_string} barring any issues. <br><br> {html_notes}"
+    )
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
diff -Nru openexr-3.4.6+ds/share/util/release/changes.py openexr-3.4.14/share/util/release/changes.py
--- openexr-3.4.6+ds/share/util/release/changes.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/changes.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,288 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Add or update a release section in CHANGES.md with merged PR entries."""
+
+from __future__ import annotations
+
+import json
+import re
+import sys
+from datetime import datetime, timedelta
+from subprocess import PIPE, run
+
+from _common import (
+    changes_anchor_date_slug,
+    format_month_day_year,
+    prev_patch_version,
+    require_repo_url,
+)
+from _security import (
+    collect_security_refs_for_prs,
+    gh_security_advisories_cve_titles,
+    oss_fuzz_issue_titles,
+)
+
+MERGED_PR_HEADING_RE = re.compile(
+    r"^###\s+Merged Pull Requests\s*:?\s*$", re.IGNORECASE
+)
+MERGED_WORKFLOW_HEADING_RE = re.compile(
+    r"^###\s+Merged Workflow Pull Requests\s*:?\s*$", re.IGNORECASE
+)
+SECURITY_HEADING_RE = re.compile(r"^###\s+Security\s*:?\s*$", re.IGNORECASE)
+PR_BULLET_RE = re.compile(r"^\*\s*\[(\d+)\]\(")
+
+
+def gh_pr_view(pr_number: str) -> dict:
+    result = run(
+        [
+            "gh",
+            "pr",
+            "view",
+            pr_number,
+            "--json",
+            "title,author",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr view failed\n")
+        sys.exit(1)
+    return json.loads(result.stdout)
+
+
+def strip_security_from_heading(heading_lines: list[str]) -> list[str]:
+    out: list[str] = []
+    i = 0
+    n = len(heading_lines)
+    while i < n:
+        if SECURITY_HEADING_RE.match(heading_lines[i].strip()):
+            if out and not out[-1].strip():
+                out.pop()
+            i += 1
+            while i < n and not heading_lines[i].startswith("### "):
+                i += 1
+            continue
+        out.append(heading_lines[i])
+        i += 1
+    return out
+
+
+def parse_pr_blocks_dict(lines, i, stop_at_workflow_heading):
+    out = {}
+    n = len(lines)
+    while i < n:
+        line = lines[i]
+        if stop_at_workflow_heading and MERGED_WORKFLOW_HEADING_RE.match(
+            line.strip()
+        ):
+            break
+        mo = PR_BULLET_RE.match(line.strip())
+        if mo:
+            pr = mo.group(1)
+            bullet = line
+            if i + 1 < n:
+                nxt = lines[i + 1]
+                if stop_at_workflow_heading and MERGED_WORKFLOW_HEADING_RE.match(
+                    nxt.strip()
+                ):
+                    out[pr] = bullet
+                    i += 1
+                    continue
+                if PR_BULLET_RE.match(nxt.strip()):
+                    out[pr] = bullet
+                    i += 1
+                    continue
+                out[pr] = "\n".join([bullet, nxt])
+                i += 2
+                continue
+            out[pr] = bullet
+            i += 1
+            continue
+        i += 1
+    return out, i
+
+
+def parse_section(lines):
+    heading = []
+    i = 0
+    n = len(lines)
+
+    while i < n:
+        line = lines[i]
+        if MERGED_PR_HEADING_RE.match(line.strip()):
+            break
+        heading.append(line)
+        i += 1
+
+    merged_prs = {}
+    if i < n and MERGED_PR_HEADING_RE.match(lines[i].strip()):
+        i += 1
+        merged_prs, i = parse_pr_blocks_dict(lines, i, stop_at_workflow_heading=True)
+
+    merged_workflow_prs = {}
+    if i < n and MERGED_WORKFLOW_HEADING_RE.match(lines[i].strip()):
+        i += 1
+        merged_workflow_prs, i = parse_pr_blocks_dict(
+            lines, i, stop_at_workflow_heading=False
+        )
+
+    return heading, merged_prs, merged_workflow_prs
+
+
+def pr_is_workflow_only(pr_number: str) -> bool:
+    result = run(
+        ["gh", "pr", "view", pr_number, "--json", "files", "--jq", "[.files[].path]"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr view failed\n")
+        sys.exit(1)
+    paths = json.loads(result.stdout or "[]")
+    return bool(paths) and all(p.startswith(".github/workflows/") for p in paths)
+
+
+def main() -> None:
+    if len(sys.argv) < 3:
+        print(
+            "Usage: changes.py <tag> <pr-number> ...\n"
+            "Example: changes.py v3.4.7 1234 1235",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+
+    tag = sys.argv[1]
+    prs = sys.argv[2:]
+
+    with open("CHANGES.md", encoding="utf-8") as f:
+        lines = f.read().splitlines()
+
+    base_tag = tag.lstrip("v").split("-rc")[0]
+    prev_tag = prev_patch_version(base_tag)
+
+    section_re = re.compile(
+        rf"^##\s+Version\s+{re.escape(base_tag)}\b", re.IGNORECASE
+    )
+    prev_re = (
+        re.compile(rf"^##\s+Version\s+{re.escape(prev_tag)}\b", re.IGNORECASE)
+        if prev_tag
+        else None
+    )
+
+    section_index = None
+    footer_index = None
+    for i, line in enumerate(lines):
+        if section_index is None and section_re.match(line):
+            section_index = i
+        if prev_re is not None and footer_index is None and prev_re.match(line):
+            footer_index = i
+
+    header_index = section_index if section_index is not None else footer_index
+    if header_index is None:
+        print(
+            "Could not locate insertion point (no matching version section and no "
+            "previous patch release heading in CHANGES.md).",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+
+    release_date = datetime.now() + timedelta(days=2)
+    date_str = format_month_day_year(release_date)
+
+    if section_index is not None:
+        section_heading, merged_prs, merged_workflow_prs = parse_section(
+            lines[section_index:footer_index]
+        )
+        section_heading = strip_security_from_heading(section_heading)
+    else:
+        section_heading = [f"## Version {base_tag} ({date_str})\n"]
+        merged_prs = {}
+        merged_workflow_prs = {}
+
+    toc, prev_toc = None, None
+    toc_re = re.compile(rf"^\*\s+\[Version\s+{re.escape(base_tag)}\]", re.IGNORECASE)
+    prev_toc_re = (
+        re.compile(rf"^\*\s+\[Version\s+{re.escape(prev_tag)}\]", re.IGNORECASE)
+        if prev_tag
+        else None
+    )
+    for i, line in enumerate(lines[:footer_index]):
+        if toc is None and toc_re.match(line):
+            toc = i
+        elif prev_toc is None and prev_toc_re and prev_toc_re.match(line):
+            prev_toc = i
+            break
+
+    url = require_repo_url()
+    for pr_number in prs:
+        info = gh_pr_view(pr_number)
+        title = info.get("title") or ""
+        author = (info.get("author") or {}).get("login") or ""
+        is_workflow = "dependabot" in author or pr_is_workflow_only(pr_number)
+        title_one_line = " ".join(title.split())
+        pr_block = f"* [{pr_number}]({url}/pull/{pr_number})\n{title_one_line}"
+        if is_workflow:
+            merged_workflow_prs[pr_number] = pr_block
+        else:
+            merged_prs[pr_number] = pr_block
+
+    all_prs_for_security = sorted(
+        set(merged_prs) | set(merged_workflow_prs),
+        key=int,
+        reverse=True,
+    )
+    cves, oss_fuzz_issues = collect_security_refs_for_prs(all_prs_for_security)
+
+    advisory_titles = gh_security_advisories_cve_titles()
+    oss_fuzz_titles = oss_fuzz_issue_titles(list(dict.fromkeys(oss_fuzz_issues)))
+
+    with open("CHANGES.md", "w", encoding="utf-8", newline="\n") as f:
+        if toc is None:
+            f.write("\n".join(lines[:prev_toc]) + "\n")
+            base_tag_nonum = base_tag.replace(".", "")
+            date_str_lower = changes_anchor_date_slug(release_date)
+            f.write(
+                f"* [Version {base_tag}](#version-{base_tag_nonum}-{date_str_lower}) "
+                f"{date_str}\n"
+            )
+            f.write("\n".join(lines[prev_toc:header_index]) + "\n")
+        else:
+            f.write("\n".join(lines[:header_index]) + "\n")
+        f.write("\n".join(section_heading))
+        if cves or oss_fuzz_issues:
+            f.write("\n\n### Security\n")
+            f.write(
+                "\nThis release addresses the following security "
+                "vulnerabilities:\n\n"
+            )
+            for cve in sorted(set(cves), reverse=True):
+                title = advisory_titles.get(cve, "")
+                suffix = f"\n  {title}" if title else ""
+                f.write(f"* [{cve}](https://www.cve.org/CVERecord?id={cve}){suffix}\n")
+            for issue_id in sorted(set(oss_fuzz_issues), reverse=True):
+                url_issue = f"https://issues.oss-fuzz.com/issues/{issue_id}"
+                f.write(f"* OSS-Fuzz [{issue_id}]({url_issue})\n")
+                short = oss_fuzz_titles.get(issue_id, "")
+                if short:
+                    f.write(f"  {short}\n")
+        f.write("\n### Merged Pull Requests\n\n")
+        for pr, value in sorted(merged_prs.items(), key=lambda kv: int(kv[0]), reverse=True):
+            f.write("  " + value + "\n")
+        f.write("\n### Merged Workflow Pull Requests\n\n")
+        for pr, value in sorted(merged_workflow_prs.items(), key=lambda kv: int(kv[0]), reverse=True):
+            f.write("  " + value + "\n")
+        f.write("\n" + "\n".join(lines[footer_index:]))
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
diff -Nru openexr-3.4.6+ds/share/util/release/cherry.py openexr-3.4.14/share/util/release/cherry.py
--- openexr-3.4.6+ds/share/util/release/cherry.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/cherry.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""List merged PRs with a GitHub label; print cherry-pick commands (oldest merge first).
+
+By default prints one combined ``git cherry-pick`` line plus a ``changes.py`` line.
+With ``--single``, prints one ``git cherry-pick`` command per PR instead.
+"""
+
+from __future__ import annotations
+
+import json
+import sys
+from subprocess import PIPE, run
+
+
+def main() -> None:
+    args = sys.argv[1:]
+    single = False
+    if "--single" in args:
+        single = True
+        args.remove("--single")
+    if len(args) != 1:
+        print(
+            "Usage: cherry.py [--single] <label>\n"
+            "  --single   print one git cherry-pick command per PR",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+    label = args[0]
+
+    result = run(
+        [
+            "gh",
+            "pr",
+            "list",
+            "--label",
+            label,
+            "--state",
+            "merged",
+            "--limit",
+            "500",
+            "--json",
+            "mergedAt,mergeCommit,title,number",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr list failed\n")
+        sys.exit(1)
+
+    try:
+        prs = json.loads(result.stdout or "[]")
+    except json.JSONDecodeError as exc:
+        print(f"Invalid JSON from gh: {exc}", file=sys.stderr)
+        sys.exit(1)
+
+    merged = []
+    for pr in prs:
+        pr_id = pr.get("number") or "?"
+        mc = pr.get("mergeCommit") or {}
+        oid = mc.get("oid")
+        merged_at = pr.get("mergedAt")
+        title = pr.get("title") or ""
+        if not oid or not merged_at:
+            continue
+        merged.append((merged_at, oid, title, pr_id))
+
+    # sort by merged_at
+    merged.sort(key=lambda x: x[0])
+
+    if not merged:
+        dym = f"; did you mean v{label}?" if not label.startswith('v') else ""
+        print(f"No PRs with label {label}{dym}")
+        return
+
+    if single:
+        for _merged_at, oid, title, pr_id in merged:
+            abbrev = oid[:7]
+            title_one_line = " ".join(title.split())
+            print(f"git cherry-pick {abbrev} # {title_one_line} (#{pr_id})")
+    else:
+        print(f"git cherry-pick {' '.join(o[:7] for a, o, t, i in merged)}")
+
+    pr_ids = " ".join(str(i) for a, o, t, i in merged)
+    print(f"python share/util/release/changes.py {label} {pr_ids}")
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
+
diff -Nru openexr-3.4.6+ds/share/util/release/_common.py openexr-3.4.14/share/util/release/_common.py
--- openexr-3.4.6+ds/share/util/release/_common.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/_common.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,164 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Shared helpers for OpenEXR release utility scripts."""
+
+from __future__ import annotations
+
+import re
+import sys
+from datetime import datetime
+from pathlib import Path
+from subprocess import PIPE, run
+
+
+def format_month_day_year(dt: datetime) -> str:
+    """Portable long date, e.g. ``May 7, 2026`` (no leading zero on day)."""
+    return f"{dt.strftime('%B')} {dt.day}, {dt.year}"
+
+
+def format_weekday_month_day(dt: datetime) -> str:
+    """Portable weekday date, e.g. ``Wednesday, May 7``."""
+    return f"{dt.strftime('%A, %B')} {dt.day}"
+
+
+def parse_release_date(date_text: str) -> datetime:
+    """Parse a CHANGES.md release date, tolerating extra whitespace."""
+    return datetime.strptime(" ".join(date_text.split()), "%B %d, %Y")
+
+
+def changes_anchor_date_slug(dt: datetime) -> str:
+    """Markdown anchor fragment for a release date, e.g. ``may-7-2026``."""
+    return format_month_day_year(dt).lower().replace(",", "").replace(" ", "-")
+
+
+def extract_section(content: str, version_tag: str) -> tuple[datetime | None, str]:
+    """
+    Extract the section of release notes starting with a heading like
+    ``## Version <tag>``. Include only the text in the section itself,
+    omitting subsections (merged PRs, etc.).
+    """
+    version_header = re.compile(
+        rf"^##.*Version {re.escape(version_tag)}\b.*\(([^)]+)\)",
+        re.IGNORECASE,
+    )
+    subsection_header_pattern = re.compile(r"^##\s+")
+    lines = content.splitlines()
+    capture = False
+    section_lines: list[str] = []
+    release_date = None
+
+    for line in lines:
+        m = version_header.match(line)
+        if m:
+            release_date = parse_release_date(m.group(1))
+            capture = True
+            continue
+        if capture and (
+            subsection_header_pattern.match(line)
+            or "Merged Pull Requests" in line
+            or "Merged pull requests" in line
+        ):
+            break
+        if capture:
+            section_lines.append(line)
+
+    return release_date, "\n".join(section_lines).strip()
+
+
+def apply_release_note_emojis(release_notes: str) -> str:
+    """Replace :emoji: shortcodes in release notes with Unicode symbols."""
+    subs = (
+        (r":bug:", "🐛"),
+        (r":rocket:", "🚀"),
+        (r":hammer_and_wrench:", "🛠️"),
+        (r":wrench:", "🔧"),
+        (r":sparkles:", "✨"),
+        (r":snake:", "🐍"),
+        (r":package:", "📦"),
+        (r":warning:", "⚠️"),
+        (r":book:", "📖"),
+    )
+    for pattern, repl in subs:
+        release_notes = re.sub(pattern, repl, release_notes, flags=re.DOTALL)
+    return release_notes
+
+
+def parse_release_version(tag: str) -> str:
+    """Strip leading ``v`` and trailing ``-rc*`` from a release tag."""
+    return tag.lstrip("v").split("-rc")[0]
+
+
+def _strip_git_suffix(url: str) -> str:
+    url = url.strip()
+    if url.endswith(".git"):
+        return url[: -len(".git")]
+    return url
+
+
+def get_repo_url() -> str | None:
+    try:
+        result = run(
+            ["git", "config", "--get", "remote.upstream.url"],
+            stdout=PIPE,
+            stderr=PIPE,
+            universal_newlines=True,
+            check=False,
+        )
+        url = _strip_git_suffix(result.stdout or "")
+        if url:
+            return url
+        result = run(
+            ["git", "config", "--get", "remote.origin.url"],
+            stdout=PIPE,
+            stderr=PIPE,
+            universal_newlines=True,
+            check=False,
+        )
+        return _strip_git_suffix(result.stdout or "") or None
+    except OSError:
+        return None
+
+
+def require_repo_url() -> str:
+    url = get_repo_url()
+    if not url:
+        print(
+            "Could not determine repository URL from git remotes.",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+    return url
+
+
+def load_release_notes(tag: str) -> tuple[datetime, str, str]:
+    """
+    Read ``CHANGES.md`` and return ``(release_date, release_notes, release_version)``.
+    Exits the process if the section is missing.
+    """
+    release_version = parse_release_version(tag)
+    changes_path = Path("CHANGES.md")
+    if not changes_path.is_file():
+        print("CHANGES.md not found.", file=sys.stderr)
+        sys.exit(1)
+    content = changes_path.read_text(encoding="utf-8")
+    release_date, release_notes = extract_section(content, release_version)
+    if release_date is None:
+        print(f"No release notes found in CHANGES.md for {tag}", file=sys.stderr)
+        sys.exit(1)
+    release_notes = apply_release_note_emojis(release_notes)
+    return release_date, release_notes, release_version
+
+
+def prev_patch_version(base_tag: str) -> str | None:
+    """
+    Previous release in the same major.minor line, e.g. ``3.4.5`` -> ``3.4.4``.
+    """
+    parts = base_tag.split(".")
+    if not parts or not parts[-1].isdigit():
+        return None
+    patch = int(parts[-1])
+    if patch <= 0:
+        return None
+    parts[-1] = str(patch - 1)
+    return ".".join(parts)
diff -Nru openexr-3.4.6+ds/share/util/release/draft.py openexr-3.4.14/share/util/release/draft.py
--- openexr-3.4.6+ds/share/util/release/draft.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/draft.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Create a draft GitHub release for a tagged release."""
+
+from __future__ import annotations
+
+import sys
+from subprocess import run
+
+from _common import load_release_notes
+
+
+def create_draft_release(tag: str, release_notes: str) -> None:
+    release_tag = tag.split("-rc")[0]
+    run(
+        ["gh", "release", "create", tag, "--draft", "--title", release_tag, "-F", "-"],
+        input=release_notes,
+        text=True,
+        check=True,
+    )
+
+
+def main() -> None:
+    if len(sys.argv) < 2:
+        print("Usage: draft.py <tag>   e.g. draft.py v3.4.7", file=sys.stderr)
+        sys.exit(1)
+    tag = sys.argv[1]
+    _release_date, release_notes, _release_version = load_release_notes(tag)
+    create_draft_release(tag, release_notes)
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
+
diff -Nru openexr-3.4.6+ds/share/util/release/__init__.py openexr-3.4.14/share/util/release/__init__.py
--- openexr-3.4.6+ds/share/util/release/__init__.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/__init__.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""OpenEXR release automation scripts (see share/util/release.py)."""
diff -Nru openexr-3.4.6+ds/share/util/release/log.py openexr-3.4.14/share/util/release/log.py
--- openexr-3.4.6+ds/share/util/release/log.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/log.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Print git log annotated with release labels and security refs from each commit's PR."""
+
+from __future__ import annotations
+
+import json
+import re
+import sys
+from subprocess import PIPE, run
+
+from _security import pr_addressed_cves
+
+LOG_PR_SUFFIX_RE = re.compile(r"\(#(\d+)\)\s*$")
+
+
+def pr_labels(line: str) -> tuple[str, str]:
+    m = LOG_PR_SUFFIX_RE.search(line)
+    if not m:
+        return "", ""
+    pr_number = m.group(1)
+    result = run(
+        ["gh", "pr", "view", pr_number, "--json", "labels"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        return pr_number, ""
+    try:
+        data = json.loads(result.stdout or "{}")
+    except json.JSONDecodeError:
+        return pr_number, ""
+    out = []
+    for lab in data.get("labels") or []:
+        name = lab.get("name") or ""
+        if name.startswith("v"):
+            out.append(name)
+    return pr_number, " ".join(out)
+
+
+def main() -> None:
+    if len(sys.argv) > 1:
+        print("Usage: python share/util/release/log.py", file=sys.stderr)
+        sys.exit(1)
+        
+    result = run(
+        ["git", "log", "--pretty=format:%h %s"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "git log failed\n")
+        sys.exit(1)
+
+    for line in result.stdout.splitlines():
+        pr, labels = pr_labels(line)
+        pr_cves, pr_oss_fuzz = pr_addressed_cves(pr)
+        refs = pr_cves + [f"oss-fuzz:{i}" for i in pr_oss_fuzz]
+        suffix = f" [{' '.join(refs)}]" if refs else ""
+        print(f"{labels:7} {line}{suffix}")
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
diff -Nru openexr-3.4.6+ds/share/util/release/news.py openexr-3.4.14/share/util/release/news.py
--- openexr-3.4.6+ds/share/util/release/news.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/news.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Update website/news.rst and latest_news_title.rst for a release."""
+
+from __future__ import annotations
+
+import re
+import sys
+from datetime import datetime
+from subprocess import PIPE, run
+
+from _common import format_month_day_year, load_release_notes
+
+
+def markdown_to_rst(markdown_text: str) -> str:
+    markdown_text = re.sub(r"(?<!`)`([^`]+)`(?!`)", r"``\1``", markdown_text)
+    markdown_text = re.sub(
+        r"\[([\s\S]*?)\]\(([\s\S]*?)\)",
+        r"`\1 <\2>`_",
+        markdown_text,
+    )
+    emoji_subs = (
+        (r":bug:", "🐛"),
+        (r":rocket:", "🚀"),
+        (r":hammer_and_wrench:", "🛠️"),
+        (r":wrench:", "🔧"),
+        (r":sparkles:", "✨"),
+        (r":snake:", "🐍"),
+        (r":package:", "📦"),
+        (r":warning:", "⚠️"),
+    )
+    for pattern, repl in emoji_subs:
+        markdown_text = re.sub(pattern, repl, markdown_text, flags=re.DOTALL)
+
+    rst_lines = []
+    for line in markdown_text.splitlines():
+        if line.startswith("- "):
+            line = line.replace("- ", "* ", 1)
+        line = re.sub(r"\*\*(.*?)\*\*", r"**\1**", line)
+        line = re.sub(r"\*(.*?)\*", r"*\1*", line)
+        rst_lines.append(line)
+    return "\n".join(rst_lines).strip()
+
+
+def _git_show(path: str) -> str:
+    result = run(
+        ["git", "show", f"HEAD:{path}"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(
+            result.stderr or f"git show HEAD:{path} failed\n"
+        )
+        sys.exit(1)
+    return result.stdout
+
+
+def _parse_latest_news_title(content: str) -> str:
+    for line in reversed(content.splitlines()):
+        if "replace:: " in line:
+            title = line.split("replace:: ", 1)[1].strip()
+            return title.lstrip("**").rstrip("**")
+    print(
+        "Could not parse previous title from website/latest_news_title.rst",
+        file=sys.stderr,
+    )
+    sys.exit(1)
+
+
+def update_news_file(
+    release_notes: str, tag: str, release_date: datetime
+) -> None:
+    date_str = format_month_day_year(release_date)
+    new_section_title = f"{date_str} - OpenEXR {tag} Released"
+
+    old_section_title = _parse_latest_news_title(
+        _git_show("website/latest_news_title.rst")
+    )
+
+    with open("website/latest_news_title.rst", "w", encoding="utf-8") as f:
+        f.write("..\n")
+        f.write("  SPDX-License-Identifier: BSD-3-Clause\n")
+        f.write("  Copyright (c) Contributors to the OpenEXR Project.\n")
+        f.write(f".. |latest-news-title| replace:: **{new_section_title}**")
+
+    content = _git_show("website/news.rst")
+    old_news = re.split(r"^=+\s*$", content, maxsplit=1, flags=re.MULTILINE)
+    if len(old_news) < 2:
+        print(
+            "Unexpected format in website/news.rst (missing section divider)",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+    old_news[1] = re.sub(
+        r"\.\. _LatestNewsStart:\n", "", old_news[1], flags=re.DOTALL
+    )
+    old_news[1] = re.sub(
+        r"\.\. _LatestNewsEnd:\n", "", old_news[1], flags=re.DOTALL
+    )
+
+    with open("website/news.rst", "w", encoding="utf-8") as f:
+        f.write(old_news[0])
+        f.write("=" * len(new_section_title) + "\n\n")
+        f.write(".. _LatestNewsStart:\n\n")
+        f.write(release_notes + "\n\n")
+        f.write(".. _LatestNewsEnd:\n\n")
+        f.write(old_section_title + "\n")
+        f.write("=" * len(old_section_title))
+        f.write(old_news[1])
+
+
+def main() -> None:
+    if len(sys.argv) < 2:
+        print("Usage: news.py <tag>   e.g. news.py v3.4.8", file=sys.stderr)
+        sys.exit(1)
+    tag = sys.argv[1]
+    release_date, release_notes, release_version = load_release_notes(tag)
+    rst_text = markdown_to_rst(release_notes)
+    update_news_file(rst_text, release_version, release_date)
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
diff -Nru openexr-3.4.6+ds/share/util/release/notes.py openexr-3.4.14/share/util/release/notes.py
--- openexr-3.4.6+ds/share/util/release/notes.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/notes.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Extract release notes from CHANGES.md for a tagged release and print to stdout."""
+
+from __future__ import annotations
+
+import sys
+
+from _common import load_release_notes
+
+
+def main() -> None:
+    if len(sys.argv) < 2:
+        print("Usage: notes.py <tag>   e.g. notes.py v3.4.7", file=sys.stderr)
+        sys.exit(1)
+    _release_date, release_notes, _release_version = load_release_notes(sys.argv[1])
+    print(release_notes)
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
+
diff -Nru openexr-3.4.6+ds/share/util/release/_security.py openexr-3.4.14/share/util/release/_security.py
--- openexr-3.4.6+ds/share/util/release/_security.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/_security.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,225 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""CVE and OSS-Fuzz reference scanning for release notes (used by changes and log)."""
+
+from __future__ import annotations
+
+import json
+import re
+import sys
+from subprocess import PIPE, run
+from urllib.error import HTTPError, URLError
+from urllib.request import urlopen
+
+OSS_FUZZ_ISSUE_JSON_PREFIX = ")]}'"
+OSS_FUZZ_ISSUE_API = "https://issues.oss-fuzz.com/action/issues/{issue_id}?format=json"
+
+PR_LINE_ADDRESS_RE = re.compile(r"(?i)\bAddress(?:es|ed|ing)?\b")
+CVE_ID_RE = re.compile(r"CVE-\d{4}-\d+", re.IGNORECASE)
+OSS_FUZZ_ISSUE_RE = re.compile(
+    r"https://issues\.oss-fuzz\.com/issues/(\d+)", re.IGNORECASE
+)
+
+
+def _collect_addressed_security_refs(
+    blocks: list[str],
+) -> tuple[list[str], list[str]]:
+    cves: list[str] = []
+    cves_seen: set[str] = set()
+    oss_fuzz: list[str] = []
+    oss_seen: set[str] = set()
+    for block in blocks:
+        for line in block.splitlines():
+            if not PR_LINE_ADDRESS_RE.search(line):
+                continue
+            for m in CVE_ID_RE.finditer(line):
+                cve = m.group(0).upper()
+                if cve not in cves_seen:
+                    cves_seen.add(cve)
+                    cves.append(cve)
+            for m in OSS_FUZZ_ISSUE_RE.finditer(line):
+                issue_id = m.group(1)
+                if issue_id not in oss_seen:
+                    oss_seen.add(issue_id)
+                    oss_fuzz.append(issue_id)
+    return cves, oss_fuzz
+
+
+def _oss_fuzz_short_title(full_title: str) -> str:
+    parts = full_title.split(":", 2)
+    if len(parts) >= 3:
+        return parts[2].strip()
+    return full_title.strip()
+
+
+def _oss_fuzz_full_title_from_fetch(data, issue_id: str) -> str | None:
+    if isinstance(data, dict):
+        return None
+    target = int(issue_id)
+
+    def walk(obj):
+        if isinstance(obj, list):
+            if (
+                len(obj) >= 3
+                and obj[0] is None
+                and obj[1] == target
+                and isinstance(obj[2], list)
+                and len(obj[2]) > 5
+                and isinstance(obj[2][5], str)
+            ):
+                return obj[2][5]
+            for item in obj:
+                found = walk(item)
+                if found:
+                    return found
+        return None
+
+    return walk(data)
+
+
+def _fetch_oss_fuzz_issue_json(issue_id: str):
+    url = OSS_FUZZ_ISSUE_API.format(issue_id=issue_id)
+    try:
+        with urlopen(url, timeout=30) as resp:
+            raw = resp.read().decode()
+    except (HTTPError, URLError, TimeoutError, OSError) as exc:
+        print(
+            f"Warning: could not fetch OSS-Fuzz issue {issue_id}: {exc}",
+            file=sys.stderr,
+        )
+        return None
+    if raw.startswith(OSS_FUZZ_ISSUE_JSON_PREFIX):
+        raw = raw[len(OSS_FUZZ_ISSUE_JSON_PREFIX) :]
+    try:
+        return json.loads(raw)
+    except json.JSONDecodeError:
+        print(
+            f"Warning: invalid JSON fetching OSS-Fuzz issue {issue_id}",
+            file=sys.stderr,
+        )
+        return None
+
+
+def oss_fuzz_issue_titles(issue_ids: list[str]) -> dict[str, str]:
+    titles: dict[str, str] = {}
+    for issue_id in issue_ids:
+        data = _fetch_oss_fuzz_issue_json(issue_id)
+        if data is None:
+            continue
+        if isinstance(data, dict) and data.get("message"):
+            print(
+                f"Warning: OSS-Fuzz issue {issue_id}: {data['message']}",
+                file=sys.stderr,
+            )
+            continue
+        full = _oss_fuzz_full_title_from_fetch(data, issue_id)
+        if full:
+            titles[issue_id] = _oss_fuzz_short_title(full)
+    return titles
+
+
+def _gh_pr_text_blocks(pr_number: str) -> list[str]:
+    result = run(
+        [
+            "gh",
+            "pr",
+            "view",
+            pr_number,
+            "--json",
+            "body,comments,reviews",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr view failed\n")
+        sys.exit(1)
+    data = json.loads(result.stdout or "{}")
+    blocks: list[str] = []
+    body = data.get("body")
+    if body:
+        blocks.append(body)
+    for c in data.get("comments") or []:
+        if isinstance(c, dict):
+            b = c.get("body")
+            if b:
+                blocks.append(b)
+    for r in data.get("reviews") or []:
+        if isinstance(r, dict):
+            b = r.get("body")
+            if b:
+                blocks.append(b)
+    return blocks
+
+
+def pr_addressed_cves(pr_number: str) -> tuple[list[str], list[str]]:
+    if not pr_number:
+        return [], []
+    return _collect_addressed_security_refs(_gh_pr_text_blocks(pr_number))
+
+
+def gh_security_advisories_cve_titles() -> dict[str, str]:
+    r = run(
+        ["gh", "repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if r.returncode != 0:
+        return {}
+    nwo = (r.stdout or "").strip()
+    if "/" not in nwo:
+        return {}
+
+    r2 = run(
+        [
+            "gh",
+            "api",
+            f"repos/{nwo}/security-advisories",
+            "--paginate",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if r2.returncode != 0:
+        return {}
+
+    try:
+        items = json.loads(r2.stdout or "[]")
+    except json.JSONDecodeError:
+        return {}
+
+    if not isinstance(items, list):
+        return {}
+
+    out: dict[str, str] = {}
+    for item in items:
+        if not isinstance(item, dict):
+            continue
+        cve = item.get("cve_id")
+        if not cve or not isinstance(cve, str):
+            continue
+        key = cve.strip().upper()
+        summary = item.get("summary")
+        title = summary.strip() if isinstance(summary, str) else ""
+        if key not in out:
+            out[key] = title
+    return out
+
+
+def collect_security_refs_for_prs(
+    pr_numbers: list[str],
+) -> tuple[list[str], list[str]]:
+    cves: list[str] = []
+    oss_fuzz_issues: list[str] = []
+    for pr_number in pr_numbers:
+        pr_cves, pr_oss_fuzz = pr_addressed_cves(pr_number)
+        cves.extend(pr_cves)
+        oss_fuzz_issues.extend(pr_oss_fuzz)
+    return cves, oss_fuzz_issues
diff -Nru openexr-3.4.6+ds/share/util/release/tag.py openexr-3.4.14/share/util/release/tag.py
--- openexr-3.4.6+ds/share/util/release/tag.py	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/share/util/release/tag.py	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright Contributors to the OpenEXR Project.
+
+"""Create a signed annotated git tag using release notes from CHANGES.md."""
+
+from __future__ import annotations
+
+import sys
+from subprocess import run
+
+from _common import format_month_day_year, load_release_notes
+
+
+def main() -> None:
+    if len(sys.argv) < 2:
+        print("Usage: tag.py <tag>   e.g. tag.py v3.4.7", file=sys.stderr)
+        sys.exit(1)
+    tag = sys.argv[1]
+    release_date, release_notes, _release_version = load_release_notes(tag)
+    tag_message = f"{tag} - {format_month_day_year(release_date)}\n{release_notes}\n"
+    run(["git", "tag", "-s", tag, "-F", "-"], input=tag_message, text=True, check=True)
+    run(["git", "tag", "-v", tag], check=True)
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(130)
+
diff -Nru openexr-3.4.6+ds/share/util/release.py openexr-3.4.14/share/util/release.py
--- openexr-3.4.6+ds/share/util/release.py	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/share/util/release.py	2026-08-05 04:29:55.000000000 +0300
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright Contributors to the OpenEXR Project.
 
@@ -8,7 +8,10 @@
 # `release.py news <tag>` - edit the website/news.rst file to add reference to the tagged release
 # `release.py draft <tag>` - create a draft release on GitHub
 # `release.py candidate <tag>` - format a message about the upcoming release, print to stdout
-
+# `release.py tag <tag>` - create a signed annotated git tag with the release notes as the tag message
+# `release.py cherry <label>` - list merged PRs with the given label, print cherry-pick lines (oldest merge first)
+# `release.py changes <tag> [pr#> ... ]` - add section to CHANGES.md with given PRs
+# `release.py log` - print a `git log` annotated with the labels from each commit's associated PR, if there is one.
 #
 # The file `CHANGES.md` is assumed to old the release notes. When
 # preparing a release, edit this file by hand to add a description of
@@ -22,12 +25,20 @@
 # main website landing page to reproduce the text.
 #
 
+import json
 import sys
 import re
+import subprocess
+from pathlib import Path
 from subprocess import PIPE, run
-from datetime import datetime
+from datetime import datetime, timedelta
+from urllib.error import HTTPError, URLError
+from urllib.request import urlopen
 import markdown
 
+OSS_FUZZ_ISSUE_JSON_PREFIX = ")]}'"
+OSS_FUZZ_ISSUE_API = "https://issues.oss-fuzz.com/action/issues/{issue_id}?format=json"
+
 def extract_section(content, version_tag):
     """Extract the section of release notes starting with a heading
     like "Version <tag>".  Include only the text in the section
@@ -69,8 +80,16 @@
     """
     Convert simple Markdown text to reStructuredText (reST) manually.
     """
+
+    # Convert `text` to ``text`` (but skip existing ``)
+    markdown_text = re.sub(r'(?<!`)`([^`]+)`(?!`)', r'``\1``', markdown_text)
+
     # Replace markdown links [text](url) with reST format `text <url>`_
-    markdown_text = re.sub(r'\[(.*?)\]\((.*?)\)', r'`\1 <\2>`_', markdown_text)
+    markdown_text = re.sub(
+        r'\[([\s\S]*?)\]\(([\s\S]*?)\)',
+        r'`\1 <\2>`_',
+        markdown_text
+    )
 
     # Convert the special symbols
     markdown_text = re.sub(r':bug:', "🐛", markdown_text, flags=re.DOTALL)
@@ -110,6 +129,635 @@
                  check=True
                  )
 
+def get_repo_url():
+    try:
+        # Return the upstream if this is in a fork
+        result = run(['git', 'config', '--get', 'remote.upstream.url'],
+                     stdout=PIPE, stderr=PIPE, universal_newlines=True)
+        url = result.stdout.strip().rstrip(".git")
+        if url:
+            return url
+        
+        result = run(['git', 'config', '--get', 'remote.origin.url'],
+                     stdout=PIPE, stderr=PIPE, universal_newlines=True)
+        url = result.stdout.strip().rstrip(".git")
+        return url
+    except subprocess.CalledProcessError:
+        return None  # Not a git repo or no origin remote
+
+
+def cmd_cherry(label):
+    """
+    List merged PRs with the given GitHub label, ordered by merge time
+    (oldest first). Print one line per merge commit suitable for cherry-picking.
+    """
+    result = run(
+        [
+            "gh",
+            "pr",
+            "list",
+            "--label",
+            label,
+            "--state",
+            "merged",
+            "--limit",
+            "500",
+            "--json",
+            "mergedAt,mergeCommit,title,number",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr list failed\n")
+        sys.exit(1)
+
+    try:
+        prs = json.loads(result.stdout or "[]")
+    except json.JSONDecodeError as e:
+        print(f"Invalid JSON from gh: {e}", file=sys.stderr)
+        sys.exit(1)
+
+    merged = []
+    for pr in prs:
+        id = pr.get("number") or "?"
+        mc = pr.get("mergeCommit") or {}
+        oid = mc.get("oid")
+        merged_at = pr.get("mergedAt")
+        title = pr.get("title") or ""
+        if not oid or not merged_at:
+            continue
+        merged.append((merged_at, oid, title, id))
+
+    merged.sort(key=lambda x: x[0])
+
+    changes_prs = ""
+    
+    for _merged_at, oid, title, id in merged:
+        abbrev = oid[:7]
+        title_one_line = " ".join(title.split())
+        print(f"git cherry-pick {abbrev} # {title_one_line} (#{id})")
+        changes_prs += f" {id}" 
+
+    print(f"git cherry-pick {' '.join(o[:7] for a, o, t, i in merged)}")
+    print(f"release.py changes {label} {' '.join(str(id) for a, o, t, id in merged)}")
+
+def prev_patch_version(base_tag):
+    """
+    Previous release in the same major.minor line, e.g. 3.4.5 -> 3.4.4.
+    Returns None if the patch segment is missing, non-numeric, or already 0.
+    """
+    parts = base_tag.split(".")
+    if not parts or not parts[-1].isdigit():
+        return None
+    patch = int(parts[-1])
+    if patch <= 0:
+        return None
+    parts[-1] = str(patch - 1)
+    return ".".join(parts)
+
+
+def gh_pr_view(pr_number):
+    result = run(
+        [
+            "gh",
+            "pr",
+            "view",
+            pr_number,
+            "--json",
+            "title,author",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr view failed\n")
+        sys.exit(1)
+    return json.loads(result.stdout)
+
+
+def gh_security_advisories_cve_titles() -> dict[str, str]:
+    """
+    List all repository security advisories for the current repo via ``gh api``
+    (``GET .../security-advisories``, paginated). Build a dict mapping each
+    assigned ``CVE-YYYY-NNNN`` id (uppercase) to the advisory ``summary``
+    (GitHub's short title). Entries without ``cve_id`` are omitted.
+
+    Requires ``gh`` authentication with permission to read repository security
+    advisories. Returns ``{}`` if ``gh`` fails, the repo cannot be resolved, or
+    the response is not a JSON array. If multiple advisories share the same
+    ``cve_id``, the first wins.
+    """
+    r = run(
+        ["gh", "repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if r.returncode != 0:
+        return {}
+    nwo = (r.stdout or "").strip()
+    if "/" not in nwo:
+        return {}
+
+    r2 = run(
+        [
+            "gh",
+            "api",
+            f"repos/{nwo}/security-advisories",
+            "--paginate",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if r2.returncode != 0:
+        return {}
+
+    try:
+        items = json.loads(r2.stdout or "[]")
+    except json.JSONDecodeError:
+        return {}
+
+    if not isinstance(items, list):
+        return {}
+
+    out: dict[str, str] = {}
+    for item in items:
+        if not isinstance(item, dict):
+            continue
+        cve = item.get("cve_id")
+        if not cve or not isinstance(cve, str):
+            continue
+        key = cve.strip().upper()
+        summary = item.get("summary")
+        title = summary.strip() if isinstance(summary, str) else ""
+        if key not in out:
+            out[key] = title
+    return out
+
+
+# Line must contain address / addresses / addressed / addressing (word-boundary);
+# collect every CVE-YYYY-nnnn and OSS-Fuzz issue URL on that line.
+PR_LINE_ADDRESS_RE = re.compile(r"(?i)\bAddress(?:es|ed|ing)?\b")
+CVE_ID_RE = re.compile(r"CVE-\d{4}-\d+", re.IGNORECASE)
+OSS_FUZZ_ISSUE_RE = re.compile(
+    r"https://issues\.oss-fuzz\.com/issues/(\d+)", re.IGNORECASE
+)
+
+
+def _collect_addressed_security_refs(blocks: list[str]) -> tuple[list[str], list[str]]:
+    """
+    On each line containing ``Address`` / ``Addresses`` / etc., collect CVE ids
+    and OSS-Fuzz issue ids (from ``https://issues.oss-fuzz.com/issues/<id>`` URLs).
+    Returns ``(cves, oss_fuzz_issue_ids)`` in first-seen order within each list.
+    """
+    cves: list[str] = []
+    cves_seen: set[str] = set()
+    oss_fuzz: list[str] = []
+    oss_seen: set[str] = set()
+    for block in blocks:
+        for line in block.splitlines():
+            if not PR_LINE_ADDRESS_RE.search(line):
+                continue
+            for m in CVE_ID_RE.finditer(line):
+                cve = m.group(0).upper()
+                if cve not in cves_seen:
+                    cves_seen.add(cve)
+                    cves.append(cve)
+            for m in OSS_FUZZ_ISSUE_RE.finditer(line):
+                issue_id = m.group(1)
+                if issue_id not in oss_seen:
+                    oss_seen.add(issue_id)
+                    oss_fuzz.append(issue_id)
+    return cves, oss_fuzz
+
+
+def _oss_fuzz_short_title(full_title: str) -> str:
+    """
+    Strip the ``project:fuzzer:`` prefix from an OSS-Fuzz issue title, leaving
+    the crash description (matches manual CHANGES.md style).
+    """
+    parts = full_title.split(":", 2)
+    if len(parts) >= 3:
+        return parts[2].strip()
+    return full_title.strip()
+
+
+def _oss_fuzz_full_title_from_fetch(data, issue_id: str) -> str | None:
+    """Extract the full issue title from an IssueFetchResponse JSON payload."""
+    if isinstance(data, dict):
+        return None
+    target = int(issue_id)
+
+    def walk(obj):
+        if isinstance(obj, list):
+            if (
+                len(obj) >= 3
+                and obj[0] is None
+                and obj[1] == target
+                and isinstance(obj[2], list)
+                and len(obj[2]) > 5
+                and isinstance(obj[2][5], str)
+            ):
+                return obj[2][5]
+            for item in obj:
+                found = walk(item)
+                if found:
+                    return found
+        return None
+
+    return walk(data)
+
+
+def _fetch_oss_fuzz_issue_json(issue_id: str):
+    """
+    Fetch issue metadata from issues.oss-fuzz.com. Public issues return JSON;
+    private or restricted issues may return HTTP 403 or a JSON error object.
+    """
+    url = OSS_FUZZ_ISSUE_API.format(issue_id=issue_id)
+    try:
+        with urlopen(url, timeout=30) as resp:
+            raw = resp.read().decode()
+    except (HTTPError, URLError, TimeoutError, OSError) as exc:
+        print(
+            f"Warning: could not fetch OSS-Fuzz issue {issue_id}: {exc}",
+            file=sys.stderr,
+        )
+        return None
+    if raw.startswith(OSS_FUZZ_ISSUE_JSON_PREFIX):
+        raw = raw[len(OSS_FUZZ_ISSUE_JSON_PREFIX) :]
+    try:
+        return json.loads(raw)
+    except json.JSONDecodeError:
+        print(
+            f"Warning: invalid JSON fetching OSS-Fuzz issue {issue_id}",
+            file=sys.stderr,
+        )
+        return None
+
+
+def oss_fuzz_issue_titles(issue_ids: list[str]) -> dict[str, str]:
+    """
+    Map OSS-Fuzz issue id to a short title (crash description only) for use in
+    release notes. Issues that cannot be fetched are omitted from the dict.
+    """
+    titles: dict[str, str] = {}
+    for issue_id in issue_ids:
+        data = _fetch_oss_fuzz_issue_json(issue_id)
+        if data is None:
+            continue
+        if isinstance(data, dict) and data.get("message"):
+            print(
+                f"Warning: OSS-Fuzz issue {issue_id}: {data['message']}",
+                file=sys.stderr,
+            )
+            continue
+        full = _oss_fuzz_full_title_from_fetch(data, issue_id)
+        if full:
+            titles[issue_id] = _oss_fuzz_short_title(full)
+    return titles
+
+
+def _gh_pr_text_blocks(pr_number: str) -> list[str]:
+    result = run(
+        [
+            "gh",
+            "pr",
+            "view",
+            pr_number,
+            "--json",
+            "body,comments,reviews",
+        ],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr view failed\n")
+        sys.exit(1)
+    data = json.loads(result.stdout or "{}")
+    blocks: list[str] = []
+    body = data.get("body")
+    if body:
+        blocks.append(body)
+    for c in data.get("comments") or []:
+        if isinstance(c, dict):
+            b = c.get("body")
+            if b:
+                blocks.append(b)
+    for r in data.get("reviews") or []:
+        if isinstance(r, dict):
+            b = r.get("body")
+            if b:
+                blocks.append(b)
+    return blocks
+
+
+def pr_addressed_cves(pr_number: str) -> tuple[list[str], list[str]]:
+    """
+    Scan the GitHub PR description, issue comments, and review bodies.
+    On each line that contains ``Address``, ``Addresses``, ``address``, etc.
+    (case-insensitive, word-boundary match), collect:
+
+    * every ``CVE-<year>-<number>`` substring
+    * every OSS-Fuzz issue referenced as
+      ``https://issues.oss-fuzz.com/issues/<id>``
+
+    Return ``(cves, oss_fuzz_issue_ids)``, each list in first-seen order.
+    Empty lists if nothing matches.
+
+    This approximates prose such as "Addresses CVE-2024-12345" or
+    "Addresses https://issues.oss-fuzz.com/issues/512314697".
+    """
+
+    if not pr_number:
+        return [], []
+
+    return _collect_addressed_security_refs(_gh_pr_text_blocks(pr_number))
+
+
+MERGED_PR_HEADING_RE = re.compile(
+    r"^###\s+Merged Pull Requests\s*:?\s*$", re.IGNORECASE
+)
+MERGED_WORKFLOW_HEADING_RE = re.compile(
+    r"^###\s+Merged Workflow Pull Requests\s*:?\s*$", re.IGNORECASE
+)
+SECURITY_HEADING_RE = re.compile(r"^###\s+Security\s*:?\s*$", re.IGNORECASE)
+PR_BULLET_RE = re.compile(r"^\*\s*\[(\d+)\]\(")
+
+
+def strip_security_from_heading(heading_lines: list[str]) -> list[str]:
+    """
+    Remove an existing ``### Security`` subsection from release section heading
+    lines so ``cmd_changes`` can regenerate it from current PR references.
+
+    Preserves a single blank line that separated descriptive text from
+    ``### Security``; that blank is reinserted when the section is rewritten.
+    """
+    out: list[str] = []
+    i = 0
+    n = len(heading_lines)
+    while i < n:
+        if SECURITY_HEADING_RE.match(heading_lines[i].strip()):
+            # Drop the blank line between descriptive text and ### Security.
+            if out and not out[-1].strip():
+                out.pop()
+            i += 1
+            while i < n and not heading_lines[i].startswith("### "):
+                i += 1
+            continue
+        out.append(heading_lines[i])
+        i += 1
+    return out
+
+
+def collect_security_refs_for_prs(pr_numbers: list[str]) -> tuple[list[str], list[str]]:
+    """
+    Scan the given PR numbers for CVE and OSS-Fuzz references. Returns
+    ``(cves, oss_fuzz_issue_ids)`` in first-seen order (deduped per list).
+    """
+    cves: list[str] = []
+    oss_fuzz_issues: list[str] = []
+    for pr_number in pr_numbers:
+        pr_cves, pr_oss_fuzz = pr_addressed_cves(pr_number)
+        cves.extend(pr_cves)
+        oss_fuzz_issues.extend(pr_oss_fuzz)
+    return cves, oss_fuzz_issues
+
+
+def parse_pr_blocks_dict(lines, i, stop_at_workflow_heading):
+    """
+    From index *i*, read ``* [pr](url)`` entries. Each value is the bullet line and
+    the following line joined with ``\\n`` when that following line is not another
+    bullet or (if applicable) the workflow subsection heading.
+
+    If *stop_at_workflow_heading* is true, stop before ``### Merged Workflow Pull Requests``.
+    Returns ``(dict pr -> str, next_index)``.
+    """
+    out = {}
+    n = len(lines)
+    while i < n:
+        line = lines[i]
+        if stop_at_workflow_heading and MERGED_WORKFLOW_HEADING_RE.match(line.strip()):
+            break
+        mo = PR_BULLET_RE.match(line.strip())
+        if mo:
+            pr = mo.group(1)
+            bullet = line
+            if i + 1 < n:
+                nxt = lines[i + 1]
+                if stop_at_workflow_heading and MERGED_WORKFLOW_HEADING_RE.match(
+                    nxt.strip()
+                ):
+                    out[pr] = bullet
+                    i += 1
+                    continue
+                if PR_BULLET_RE.match(nxt.strip()):
+                    out[pr] = bullet
+                    i += 1
+                    continue
+                out[pr] = "\n".join([bullet, nxt])
+                i += 2
+                continue
+            out[pr] = bullet
+            i += 1
+            continue
+        i += 1
+    return out, i
+
+
+def parse_section(lines):
+    """
+    Parse a version slice of CHANGES.md (list of line strings, no trailing newlines).
+
+    Returns ``heading`` (lines before ``### Merged Pull Requests``), and two dicts
+    mapping PR number -> two-line bullet+title string for each subsection.
+    """
+    heading = []
+    i = 0
+    n = len(lines)
+
+    while i < n:
+        line = lines[i]
+        if MERGED_PR_HEADING_RE.match(line.strip()):
+            break
+        heading.append(line)
+        i += 1
+
+    merged_prs = {}
+    if i < n and MERGED_PR_HEADING_RE.match(lines[i].strip()):
+        i += 1
+        merged_prs, i = parse_pr_blocks_dict(lines, i, stop_at_workflow_heading=True)
+
+    merged_workflow_prs = {}
+    if i < n and MERGED_WORKFLOW_HEADING_RE.match(lines[i].strip()):
+        i += 1
+        merged_workflow_prs, i = parse_pr_blocks_dict(lines, i, stop_at_workflow_heading=False)
+
+    return heading, merged_prs, merged_workflow_prs
+
+def pr_is_workflow_only(pr_number: str) -> bool:
+    """Return True if every file changed by the PR is under .github/workflows/."""
+    result = run(
+        ["gh", "pr", "view", pr_number, "--json", "files",
+         "--jq", "[.files[].path]"],
+        stdout=PIPE, stderr=PIPE, universal_newlines=True, check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "gh pr view failed\n")
+        sys.exit(1)
+    paths = json.loads(result.stdout or "[]")
+    return bool(paths) and all(p.startswith(".github/workflows/") for p in paths)
+
+def cmd_changes(tag, prs):
+    """
+    Add a section to CHANGES.md for the given release if one doesn't already exist, and add the given PR 
+    to the list if it's not already there.
+    """
+
+    with open("CHANGES.md", encoding="utf-8") as f:
+        lines = f.read().splitlines()
+
+    base_tag = tag.lstrip("v").split("-rc")[0] # i.e. "3.4.7"
+    prev_tag = prev_patch_version(base_tag) # i.e.e "3.4.6"
+
+    section_re = re.compile(rf"^##\s+Version\s+{re.escape(base_tag)}\b", re.IGNORECASE)
+    prev_re = re.compile(rf"^##\s+Version\s+{re.escape(prev_tag)}\b", re.IGNORECASE)
+
+    #
+    # Terminology:
+    #  "header" = everything from the beginning of the file up to the new section
+    #  "footer" = everything afer the new section
+    #
+    
+    # Find the line index of the existing section if there is one, and
+    # to the previous release section, i.e. the release before, which
+    # comes after it in the file.
+    
+    section_index = None
+    footer_index = None # line of the following section, i.e. the remainder of the file.
+    for i, line in enumerate(lines):
+        if section_index is None and section_re.match(line):
+            section_index = i
+        if prev_re is not None and footer_index is None and prev_re.match(line):
+            footer_index = i
+
+    # header_index is the last line of content before the new section
+    header_index = ( 
+        section_index if section_index is not None else footer_index
+    )
+    if header_index is None:
+        print(
+            "Could not locate insertion point (no matching version section and no "
+            "previous patch release heading in CHANGES.md).",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+
+    release_date = datetime.now() + timedelta(days=2)
+    date_str = release_date.strftime("%B %e, %Y")
+
+    if section_index is not None:
+        # Parse the existing section into the heading (section heading
+        # and existing notes), the dict of merged PRs, and the dict of merge workflow PRs
+        section_heading, merged_prs, merged_workflow_prs = parse_section(
+            lines[section_index:footer_index]
+        )
+        section_heading = strip_security_from_heading(section_heading)
+    else:
+        # The section does not exist, so create the stub of a new one
+        section_heading = [f"## Version {base_tag} ({date_str})\n"]
+        merged_prs = {}
+        merged_workflow_prs = {}
+
+    # Find the index of the table of contents entry, if there is one.
+    toc, prev_toc = None, None
+    toc_re = re.compile(rf"^\*\s+\[Version\s+{re.escape(base_tag)}\]", re.IGNORECASE)
+    prev_toc_re = re.compile(rf"^\*\s+\[Version\s+{re.escape(prev_tag)}\]", re.IGNORECASE)
+    for i, line in enumerate(lines[:footer_index]):
+        if toc is None and toc_re.match(line):
+            toc = i
+        elif prev_toc is None and prev_toc_re.match(line):
+            prev_toc = i
+            break
+
+    # Format the list entry for each new PR:
+    # * [number](url)
+    # title
+    for pr_number in prs:
+        info = gh_pr_view(pr_number)
+        title = info.get("title") or ""
+        author = (info.get("author") or {}).get("login") or ""
+        is_workflow = "dependabot" in author or pr_is_workflow_only(pr_number)
+        url = get_repo_url()
+        title_one_line = " ".join(title.split())
+        pr_block = f"* [{pr_number}]({url}/pull/{pr_number})\n{title_one_line}"
+        # Add it to the appropriate section
+        if is_workflow:
+            merged_workflow_prs[pr_number] = pr_block
+        else:
+            merged_prs[pr_number] = pr_block
+
+    # Scan every PR listed in the section (not only those on the command line)
+    # so Security stays complete when ``changes`` is re-run incrementally.
+    all_prs_for_security = sorted(
+        set(merged_prs) | set(merged_workflow_prs),
+        key=int,
+        reverse=True,
+    )
+    cves, oss_fuzz_issues = collect_security_refs_for_prs(all_prs_for_security)
+
+    advisory_titles = gh_security_advisories_cve_titles()
+    oss_fuzz_titles = oss_fuzz_issue_titles(list(dict.fromkeys(oss_fuzz_issues)))
+
+    with open("CHANGES.md", "w", encoding="utf-8", newline="\n") as f:
+        if toc is None:
+            # No table of contents entry for this release, so add one.
+            f.write("\n".join(lines[:prev_toc]) + "\n")  # everything up to the previous release entry
+            # Write new entry
+            base_tag_nonum = base_tag.replace(".","")
+            date_str_lower = date_str.lower().replace(",","").replace(" ","-")
+            f.write(f"* [Version {base_tag}](#version-{base_tag_nonum}-{date_str_lower}) {date_str}\n")
+            # Write the rest of the table of contents and everything up to the new release section
+            f.write("\n".join(lines[prev_toc:header_index]) + "\n")  
+        else:
+            # Table of contents already has an entry, so just write the header
+            f.write("\n".join(lines[:header_index]) + "\n")
+        # Write the release section
+        f.write("\n".join(section_heading))
+        if cves or oss_fuzz_issues:
+            f.write("\n\n### Security\n")
+            f.write(
+                "\nThis release addresses the following security "
+                "vulnerabilities:\n\n"
+            )
+            for cve in sorted(set(cves), reverse=True):
+                title = advisory_titles.get(cve, "")
+                suffix = f"\n  {title}" if title else ""
+                f.write(f"* [{cve}](https://www.cve.org/CVERecord?id={cve}){suffix}\n")
+            for issue_id in sorted(set(oss_fuzz_issues), reverse=True):
+                url = f"https://issues.oss-fuzz.com/issues/{issue_id}"
+                f.write(f"* OSS-Fuzz [{issue_id}]({url})\n")
+                short = oss_fuzz_titles.get(issue_id, "")
+                if short:
+                    f.write(f"  {short}\n")
+        f.write("\n### Merged Pull Requests\n\n")
+        for pr, value in sorted(merged_prs.items(), reverse=True):
+            f.write("  " + value + "\n")
+        f.write("\n### Merged Workflow Pull Requests\n\n")
+        for pr, value in sorted(merged_workflow_prs.items(), reverse=True):
+            f.write("  " + value + "\n")
+        # Write the rest of the file
+        f.write("\n" + "\n".join(lines[footer_index:]))
+
 def update_news_file(release_notes, tag, release_date):
     """
     Update the website/news.rst file with the release notes.
@@ -186,43 +834,154 @@
     html_content = f"<h2>Release Notes</h2><p>{notes}</p>"
     return html_content
 
+def pr_labels(line):
+    """line is from `git log --pretty:format='%h %s'`, so for
+    squash/merge commits, it ends in (#<pr>).
+
+    Return the PR number and its release-related labels, i.e.
+    labels of the form 'v<major>.<minor>.<patch>'
+    """
+
+    LOG_PR_SUFFIX_RE = re.compile(r"\(#(\d+)\)\s*$")
+    m = LOG_PR_SUFFIX_RE.search(line)
+    if not m:
+        return "", ""
+    pr_number = m.group(1)
+    result = run(
+        ["gh", "pr", "view", pr_number, "--json", "labels"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        return pr_number, ""
+    try:
+        data = json.loads(result.stdout or "{}")
+    except json.JSONDecodeError:
+        return pr_number, ""
+    out = []
+    for lab in data.get("labels") or []:
+        name = lab.get("name") or ""
+        if name.startswith("v"):
+            out.append(name)
+    return pr_number, " ".join(out)
+
+def cmd_log():
+    """
+    Run ``git log`` and print commits annotated with the release labels for the associated PRs
+    """
+
+    # Output looks like:
+    # v3.4.7  3978976a Bump pypa/cibuildwheel from 3.3 to 3.4 (#2287)
+    #         b19cfec2 Bump github/codeql-action from 4.32.4 to 4.32.5 (#2286)
+    # v3.4.7  50ea61bd Fix build failure with glibc 2.43 due to C11 threads.h conflicts (#2262)
+
+    result = run(
+        ["git", "log", "--pretty=format:%h %s"],
+        stdout=PIPE,
+        stderr=PIPE,
+        universal_newlines=True,
+        check=False,
+    )
+    if result.returncode != 0:
+        sys.stderr.write(result.stderr or "git log failed\n")
+        sys.exit(1)
+
+    for line in result.stdout.splitlines():
+        pr, l = pr_labels(line)
+        pr_cves, pr_oss_fuzz = pr_addressed_cves(pr)
+        extras = " ".join(pr_cves + [f"oss-fuzz:{i}" for i in pr_oss_fuzz])
+        print(f"{l:7} {line} {extras}")
+
+def cmd_tag(tag, release_date, release_notes) -> None:
+    """
+    Create a signed annotated tag at the current ``HEAD`` using the
+    given release date and notes
+    """
+
+    tag_message = f"{tag} - {release_date}\n{release_notes}\n"
+    
+    cmd = ["git", "tag", "-s", tag, "-F", "-"]
+    run(cmd, input=tag_message, text=True, check=True)
+
+
 def main():
-    if len(sys.argv) < 3:
-        print("Usage: python release.py <notes|news|draft|candidate> <tag> [date]")
+
+    if len(sys.argv) > 1:
+        action = sys.argv[1]
+        if action == "log":
+            cmd_log()
+            return
+
+    if len(sys.argv) < 2:
+        print(
+            "Usage: python release.py "
+            "<notes|news|draft|candidate|tag|cherry|changes> ...\n"
+            "  release.py changes <tag> <pr-number>   e.g. release.py changes v3.4.7 1234\n"
+            "  release.py tag <tag> [--force]       e.g. release.py tag v3.4.7"
+        )
         sys.exit(1)
 
     action = sys.argv[1]
-    tag = sys.argv[2]
 
-    # Strip leading 'v' and trailing '-rc<candidate>' if necessary
-    base_tag = tag.lstrip('v').split('-rc')[0]
-    result = run(['git', 'tag', '--list', tag], stdout=PIPE, stderr=PIPE, universal_newlines=True)
-    if result.stdout == "":
-        tag += "-rc"
-        result = run(['git', 'tag', '--list', tag], stdout=PIPE, stderr=PIPE, universal_newlines=True)
-        if result.stdout != "":
-            print(f"Using {tag} instead...")
-        else:
-            print(f"No such tag: {tag}")
+    if action == "cherry":
+        if len(sys.argv) < 3:
+            print(
+                "Usage: python release.py cherry <label>",
+                file=sys.stderr,
+            )
             sys.exit(1)
+        cmd_cherry(sys.argv[2])
+        return
 
-    # Get the content of the CHANGES.md at the specified git tag
-    try:
-        result = run(['git', 'show', f"{tag}:CHANGES.md"], stdout=PIPE, stderr=PIPE, universal_newlines=True)
-        content = result.stdout
-        if content == None or content == "":
-            print(f"No news.txt at tag {tag}")
+    if action == "changes":
+        if len(sys.argv) < 4:
+            print(
+                "Usage: python release.py changes <tag> <pr-number>\n"
+                "Example: python release.py changes v3.4.7 1234",
+                file=sys.stderr,
+            )
             sys.exit(1)
-    except Exception as e:
-        print(f"Error: {e}")
+        tag = sys.argv[2]
+        prs = sys.argv[3:]
+        cmd_changes(tag, prs)
+        return
+
+    if len(sys.argv) < 3:
+        print(
+            "Usage: python release.py "
+            "<notes|news|draft|candidate|tag|cherry|changes> <tag-or-label> [date]"
+        )
         sys.exit(1)
 
+    tag = sys.argv[2]
+
+    # Strip leading 'v' and trailing '-rc<candidate>' if necessary
+    release_version = tag.lstrip('v').split('-rc')[0]
+
+    # Get the content of the CHANGES.md at the specified git tag
+    with open("CHANGES.md", "r") as f:
+        content = f.read()
+
     # Extract the release notes
-    release_date, release_notes = extract_section(content, base_tag)
+    release_date, release_notes = extract_section(content, release_version)
     if release_date == None:
         print("No release found.")
         return
 
+    # Convert the special symbols
+    release_notes = re.sub(r':bug:', "🐛", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':rocket:', "🚀", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':hammer_and_wrench:', "🛠️", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':wrench:', "🔧", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':sparkles:', "✨", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':snake:', "🐍", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':package:', "📦", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':warning:', "⚠️", release_notes, flags=re.DOTALL)
+    release_notes = re.sub(r':book:', "📖", release_notes, flags=re.DOTALL)
+
+
     if action == "notes":
         print(release_notes)
 
@@ -231,13 +990,19 @@
 
     elif action == "news":
         rst_text = markdown_to_rst(release_notes)
-        update_news_file(rst_text, base_tag, release_date)
+        update_news_file(rst_text, release_version, release_date)
 
     elif action == "candidate":
-        version = base_tag
         html_notes = markdown_to_html(release_notes)
         date_string = release_date.strftime("%A, %B %e")
-        print(f"OpenEXR {version} is staged for release at tag <a href=https://github.com/AcademySoftwareFoundation/openexr/releases/tag/{tag}>{tag}</a> and will be released officially {date_string} barring any issues. <br><br> {html_notes}")
-
+        url = get_repo_url()
+        project = url.split('/')[-1]
+        if project == "openexr":
+            project = "OpenEXR"
+        print(f"{project} {release_version} is staged for release at tag <a href={url}/releases/tag/{tag}>{tag}</a> and will be released officially {date_string} barring any issues. <br><br> {html_notes}")
+
+    elif action == "tag":
+        cmd_tag(tag, release_date, release_notes)
+        
 if __name__ == "__main__":
     main()
diff -Nru openexr-3.4.6+ds/share/util/vendor_openjph.sh openexr-3.4.14/share/util/vendor_openjph.sh
--- openexr-3.4.6+ds/share/util/vendor_openjph.sh	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/share/util/vendor_openjph.sh	2026-08-05 04:29:55.000000000 +0300
@@ -62,9 +62,48 @@
 rm -rf OpenJPH
 mv $OpenJPH OpenJPH
 
-# Force a static build
-sed -i '' '/^option(BUILD_SHARED_LIBS "Shared Libraries" ON)/d' OpenJPH/CMakeLists.txt
-sed -i '' 's/^add_library(openjph \${SOURCES})/add_library(openjph STATIC ${SOURCES})/' OpenJPH/src/core/CMakeLists.txt
+if [[ "$OSTYPE" == "darwin"* ]]; then
+  sed_cmd=(sed -i '')
+else
+  sed_cmd=(sed -i)
+fi
+
+# OpenEXR-specific CMake changes for bundled OpenJPH builds:
+# - remove BUILD_SHARED_LIBS option (OpenEXR controls linking)
+# - add OJPH_INSTALL option to guard install/export/pkg-config
+# - add OJPH_LIB_TYPE (STATIC/OBJECT) for bundled library type
+"${sed_cmd[@]}" '/^option(BUILD_SHARED_LIBS "Shared Libraries" ON)/d' OpenJPH/CMakeLists.txt
+
+"${sed_cmd[@]}" '/^option(OJPH_BUILD_STREAM_EXPAND/a\
+option(OJPH_INSTALL "Install OpenJPH libraries, headers, and CMake config" ON)
+' OpenJPH/CMakeLists.txt
+
+"${sed_cmd[@]}" '/^install(EXPORT openjph-targets/i\
+if (OJPH_INSTALL)
+' OpenJPH/CMakeLists.txt
+
+"${sed_cmd[@]}" '/DESTINATION ${CMAKE_INSTALL_LIBDIR}\/pkgconfig/{
+n
+a\
+endif()
+}' OpenJPH/CMakeLists.txt
+
+"${sed_cmd[@]}" '/^add_library(openjph ${SOURCES})$/c\
+if (NOT DEFINED OJPH_LIB_TYPE)\
+  set(OJPH_LIB_TYPE STATIC)\
+endif()\
+add_library(openjph ${OJPH_LIB_TYPE} ${SOURCES})
+' OpenJPH/src/core/CMakeLists.txt
+
+"${sed_cmd[@]}" '/^install(TARGETS openjph/i\
+if (OJPH_INSTALL)
+' OpenJPH/src/core/CMakeLists.txt
+
+"${sed_cmd[@]}" '/PATTERN "\*\.h"/{
+n
+a\
+endif()
+}' OpenJPH/src/core/CMakeLists.txt
 
 # Headers live under "common" in older OpenJPH releases but are included via
 # "openjph/ojph_arch.h". Rename the directory if needed.
diff -Nru openexr-3.4.6+ds/src/bin/exrmetrics/exrmetrics.cpp openexr-3.4.14/src/bin/exrmetrics/exrmetrics.cpp
--- openexr-3.4.6+ds/src/bin/exrmetrics/exrmetrics.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/bin/exrmetrics/exrmetrics.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -25,6 +25,7 @@
 
 #include <chrono>
 #include <ctime>
+#include <limits>
 #include <list>
 #include <stdexcept>
 #include <vector>
@@ -46,6 +47,25 @@
 using std::chrono::steady_clock;
 using std::isinf;
 
+namespace {
+
+//
+// Validate that count*sampleSize casts to size_t without overflow, before
+// using the value to resize a std::vector.
+//
+
+size_t
+vectorSize (uint64_t count, int sampleSize = 1)
+{
+    const uint64_t sampleSizeU = static_cast<uint64_t> (sampleSize);
+    const uint64_t maxSize     = std::numeric_limits<size_t>::max ();
+    if (sampleSizeU > 0 && count > maxSize / sampleSizeU)
+        throw IEX_NAMESPACE::OverflowExc ("Integer multiplication overflow.");
+    return static_cast<size_t> (count * sampleSizeU);
+}
+
+} // namespace
+
 double
 timing (steady_clock::time_point start, steady_clock::time_point end)
 {
@@ -80,8 +100,6 @@
     int      numChans  = channelCount (in.header ());
 
     pixelData.resize (numChans);
-    uint64_t offsetToOrigin = width * static_cast<uint64_t> (dw.min.y) +
-                              static_cast<uint64_t> (dw.min.x);
 
     int    channelNumber = 0;
     size_t rawSize       = 0;
@@ -94,13 +112,14 @@
         size_t pixelsInChannel = (width / i.channel ().xSampling) *
                                  (height / i.channel ().ySampling);
         rawSize += pixelsInChannel * samplesize;
-        pixelData[channelNumber].resize (numPixels * samplesize);
+        pixelData[channelNumber].resize (vectorSize (numPixels, samplesize));
 
         buf.insert (
             i.name (),
-            Slice (
+            Slice::Make (
                 i.channel ().type,
-                pixelData[channelNumber].data () - offsetToOrigin * samplesize,
+                pixelData[channelNumber].data (),
+                dw,
                 samplesize,
                 samplesize * width,
                 i.channel ().xSampling,
@@ -206,7 +225,7 @@
                 {
                     int samplesize = pixelTypeSize (i.channel ().type);
                     pixelData[levelIndex][channelNumber].resize (
-                        numPixels * samplesize);
+                        vectorSize (numPixels, samplesize));
 
                     buf[levelIndex].insert (
                         i.name (),
@@ -325,7 +344,7 @@
     uint64_t height    = dw.max.y + 1 - dw.min.y;
     uint64_t numPixels = width * height;
     int      numChans  = channelCount (in.header ());
-    sampleCount.resize (numPixels);
+    sampleCount.resize (vectorSize (numPixels));
 
     uint64_t offsetToOrigin = width * static_cast<uint64_t> (dw.min.y) +
                               static_cast<uint64_t> (dw.min.x);
@@ -343,7 +362,7 @@
          i != outHeader.channels ().end ();
          ++i)
     {
-        pixelPtrs[channelNumber].resize (numPixels);
+        pixelPtrs[channelNumber].resize (vectorSize (numPixels));
         int samplesize = pixelTypeSize (i.channel ().type);
         buf.insert (
             i.name (),
@@ -379,8 +398,8 @@
 
     sampleData.resize (numChans);
     channelNumber = 0;
-    for (ChannelList::ConstIterator i = in.header ().channels ().begin ();
-         i != in.header ().channels ().end ();
+    for (ChannelList::ConstIterator i = outHeader.channels ().begin ();
+         i != outHeader.channels ().end ();
          ++i)
     {
         int samplesize = pixelTypeSize (i.channel ().type);
@@ -493,7 +512,7 @@
                               static_cast<uint64_t> (dw.min.x);
 
     pixelPtrs.resize (numChans);
-    sampleCount.resize (numPixels);
+    sampleCount.resize (vectorSize (numPixels));
 
     buf.insertSampleCountSlice (Slice (
         UINT,
@@ -507,7 +526,7 @@
          i != outHeader.channels ().end ();
          ++i)
     {
-        pixelPtrs[channelNumber].resize (numPixels);
+        pixelPtrs[channelNumber].resize (vectorSize (numPixels));
         int samplesize = pixelTypeSize (i.channel ().type);
         buf.insert (
             i.name (),
@@ -764,11 +783,21 @@
     MultiPartOutputFile& out,
     vector<partData>&    parts,
     fileMetrics&         metrics,
-    bool                 logPerformance)
+    bool                 logPerformance,
+    OStream*             os = nullptr)
 {
     //
     // Call initialization functions, Read image from source.
     //
+
+    uint64_t lastPosition  = 0;
+    uint64_t currentPosition = 0;
+
+    if (os)
+    {
+        lastPosition = os->tellp();
+    }
+
     for (size_t p = 0; p < parts.size (); ++p)
     {
         string type = out.header (p).type ();
@@ -780,6 +809,7 @@
                 outpart,
                 parts[p].readBuf.scanlineBuf,
                 logPerformance ? &metrics.stats[p].writePerf : nullptr);
+            if (os) currentPosition = os->tellp();
         }
         else if (type == TILEDIMAGE)
         {
@@ -788,6 +818,7 @@
                 outpart,
                 parts[p].readBuf.tiledBuf,
                 logPerformance ? &metrics.stats[p].writePerf : nullptr);
+            if (os) currentPosition = os->tellp();
         }
         else if (type == DEEPSCANLINE)
         {
@@ -796,6 +827,7 @@
                 outpart,
                 parts[p].readBuf.deepbuf,
                 logPerformance ? &metrics.stats[p].writePerf : nullptr);
+            if (os) currentPosition = os->tellp();
         }
         else if (type == DEEPTILE)
         {
@@ -804,6 +836,13 @@
                 outpart,
                 parts[p].readBuf.deepbuf,
                 logPerformance ? &metrics.stats[p].writePerf : nullptr);
+            if (os) currentPosition = os->tellp();
+        }
+
+        if (os)
+        {
+            metrics.stats[p].sizeOnDisk = currentPosition - lastPosition;
+            lastPosition = currentPosition;
         }
     }
 }
@@ -1117,13 +1156,14 @@
             {
                 out = new MultiPartOutputFile (
                     outFileName, outHeaders.data (), outHeaders.size ());
+                writeFile (*out, parts, metrics, true);
             }
             else
             {
                 out = new MultiPartOutputFile (
                     ostream, outHeaders.data (), outHeaders.size ());
+                writeFile (*out, parts, metrics, true,&ostream);
             }
-            writeFile (*out, parts, metrics, true);
             delete out;
 
             if (reread)
diff -Nru openexr-3.4.6+ds/src/bin/exrmetrics/exrmetrics.h openexr-3.4.14/src/bin/exrmetrics/exrmetrics.h
--- openexr-3.4.6+ds/src/bin/exrmetrics/exrmetrics.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/bin/exrmetrics/exrmetrics.h	2026-08-05 04:29:55.000000000 +0300
@@ -55,6 +55,7 @@
         countRereadPerf; // for deep only, time rereading the per-pixel sample count
     std::vector<double>
         rereadPerf; // for deep, times reading the sample count, otherwise times reading the entire data
+    uint64_t sizeOnDisk = 0; // record compressed size of part on disk.
 
     partSizeData sizeData;
 };
diff -Nru openexr-3.4.6+ds/src/bin/exrmetrics/main.cpp openexr-3.4.14/src/bin/exrmetrics/main.cpp
--- openexr-3.4.6+ds/src/bin/exrmetrics/main.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/bin/exrmetrics/main.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -57,6 +57,7 @@
                "\n"
                "  -o file                     file to write to. If no file specified, uses a memory buffer\n"
                "                              note: file may be overwritten multiple times during tests\n"
+               "\n"
                "  -p n                        part number to copy, or \"all\" for all parts\n"
                "                              default is \"all\" \n"
                "\n"
@@ -72,29 +73,40 @@
             << ",orig,all\n"
                "                              default orig: retains original method)\n"
 
+               "\n"
                "  --convert                   shorthand options for writing a new file with no metrics:\n"
-               "                              -p all --type orig --time none --type orig --no-size --passes 1\n"
-               "                              change pixel type or compression by specifying --type or -z after --convert\n"
+               "                              -p all --time none --pixelmode orig --no-size --passes 1\n"
+               "                              change pixel data type/compression by specifying --pixelmode/-z after --convert\n"
+               "\n"
                "  --bench                     shorthand options for robust performance benchmarking:\n"
                "                              -p all --compression all --time write,reread --passes 10 --type half,float --no-size --csv\n"
                "\n"
                "  -16 rgba|all                [DEPRECATED] force 16 bit half float: either just RGBA, or all channels\n"
-               "                              Use --type half or --type mixed instead\n"
-               " --pixelmode list             list of pixel types to use (float,half,mixed,orig)\n"
+               "                              Use --pixelmode half or --pixelmode mixed instead\n"
+               "\n"
+               "  --pixelmode list            list of pixel data types to use (float,half,mixed,orig)\n"
                "                              mixed uses half for RGBA, float for others. Default is 'orig'\n"
-               " --time list                  comma separated list of operations to report timing for.\n"
+               "\n"
+               "  --time list                 comma separated list of operations to report timing for.\n"
                "                              operations can be any of read,write,reread (use --time none for no timing)\n"
-               " --no-size                    don't output size data\n"
-               " --json                       print output as JSON dictionary (Default mode)\n"
-               " --csv                        print output in csv mode. If passes>1, show median timing\n"
+               "\n"
+               "  --no-size                   don't output size data\n"
+               "\n"
+               "  --part-disk-size            Output the on-disk size of the data portion of each part in a multipart EXR file\n"
+               "                              (including chunk headers). File output (-o) is not supported when using this option\n"
+               "\n"
+               "  --json                      print output as JSON dictionary (Default mode)\n"
+               "\n"
+               "  --csv                       print output in csv mode. If passes>1, show median timing\n"
                "                              default is JSON mode\n"
-               " --passes num                 write and re-read file num times (default 1)\n"
+               "\n"
+               "  --passes num                write and re-read file num times (default 1)\n"
                "\n"
                "  -h, --help                  print this message\n"
+               "\n"
                "  -v                          output progress messages\n"
                "\n"
-               "  --version                   print version information\n"
-               "\n";
+               "  --version                   print version information\n";
     }
 }
 
@@ -116,6 +128,7 @@
     int                      passes  = 1;
     int                      timing  = TIME_READ | TIME_REREAD | TIME_WRITE;
     bool                     outputSizeData = true;
+    bool                     outputPartSizeOnDisk = false;
     bool                     verbose        = false;
     bool                     csv            = false;
     std::vector<PixelMode>   pixelModes;
@@ -196,6 +209,7 @@
     const partStats& data,
     const string     indent,
     int              timing,
+    bool             partSize,
     bool             raw,
     bool             stats)
 {
@@ -236,6 +250,15 @@
         out << indent << "\"re-read time\": ";
         printTiming (data.rereadPerf, out, raw, stats);
     }
+
+    if (partSize)
+    {
+        if (output) { out << ",\n"; }
+        output = true;
+        out << indent << "\"size on disk\": "
+            << data.sizeOnDisk
+            << "\n";
+    }
 }
 
 void
@@ -244,6 +267,7 @@
     list<runData>& data,
     bool           outputSizeData,
     int            timing,
+    bool           partSize,
     bool           raw,
     bool           stats)
 {
@@ -370,7 +394,7 @@
         {
             out << ",\n";
             printPartStats (
-                out, run.metrics.totalStats, "      ", timing, raw, stats);
+                out, run.metrics.totalStats, "      ", timing,false, raw, stats);
         }
         if (timing && run.metrics.stats.size () > 1)
         {
@@ -388,6 +412,7 @@
                     run.metrics.stats[part],
                     "          ",
                     timing,
+                    partSize,
                     raw,
                     stats);
                 out << "\n        }";
@@ -546,6 +571,11 @@
         return 1;
     }
 
+    // we are only able to compute part disk size if no output file is specified,
+    // causing memory output stream to be used and allowing us to measure data output.
+
+    bool showPartSizeOnDisk = opts.outputPartSizeOnDisk && !opts.outFile;
+
     if (opts.timing || opts.outputSizeData)
     {
 
@@ -556,7 +586,7 @@
         else
         {
             jsonStats (
-                cout, data, opts.outputSizeData, opts.timing, true, true);
+                cout, data, opts.outputSizeData, opts.timing,showPartSizeOnDisk, true, true);
         }
     }
 
@@ -778,6 +808,12 @@
                 cerr << "-o output filename can only be specified once\n";
                 return 1;
             }
+            if (outputPartSizeOnDisk)
+            {
+                cerr << "--part-disk-size cannot be used with file output (-o)\n";
+                return 1;
+            }
+
             outFile = argv[i + 1];
             i += 2;
         }
@@ -870,6 +906,17 @@
             outputSizeData = false;
             i += 1;
         }
+        else if (!strcmp (argv[i], "--part-disk-size"))
+        {
+            if (outFile)
+            {
+                cerr << "--part-disk-size cannot be used with file output (-o)\n";
+                return 1;
+            }
+
+            outputPartSizeOnDisk = true;
+            i += 1;
+        }
         else if (!strcmp (argv[i], "-i"))
         {
             if (i > argc - 2)
diff -Nru openexr-3.4.6+ds/src/bin/exrmultipart/exrmultipart.cpp openexr-3.4.14/src/bin/exrmultipart/exrmultipart.cpp
--- openexr-3.4.6+ds/src/bin/exrmultipart/exrmultipart.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/bin/exrmultipart/exrmultipart.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -32,7 +32,9 @@
 #include <algorithm>
 #include <assert.h>
 #include <cctype>
+#include <cstdint>
 #include <iostream>
+#include <limits>
 #include <sstream>
 #include <stdlib.h>
 #include <utility> // pair
@@ -51,6 +53,31 @@
 #    define IMF_PATH_SEPARATOR "/"
 #endif
 
+namespace {
+
+//
+// Validate that count * samplesize fits in size_t before resizing a vector.
+//
+
+size_t
+vectorSize (int64_t count, size_t samplesize)
+{
+    if (count < 0)
+        throw IEX_NAMESPACE::OverflowExc ("invalid vector size");
+
+    const size_t size_t_max = std::numeric_limits<size_t>::max ();
+    if (sizeof (size_t) < sizeof (int64_t) && count > static_cast<int64_t> (size_t_max))
+        throw IEX_NAMESPACE::OverflowExc ("invalid vector size");
+
+    const size_t c = static_cast<size_t> (count);
+    if (samplesize > 0 && c > size_t_max / samplesize)
+        throw IEX_NAMESPACE::OverflowExc ("invalid vector size");
+
+    return c * samplesize;
+}
+
+} // namespace
+
 void
 copy_tile (
     MultiPartInputFile&  input,
@@ -323,7 +350,7 @@
         // compute size of channel
         size_t samplesize = sizeof (float);
         if (chan.channel ().type == HALF) { samplesize = sizeof (half); }
-        channelstore[i].resize (samplesize * pixel_count);
+        channelstore[i].resize (vectorSize (pixel_count, samplesize));
 
         output_framebuffers[part].insert (
             output_channels[i].name,
diff -Nru openexr-3.4.6+ds/src/bin/exrmultiview/makeMultiView.cpp openexr-3.4.14/src/bin/exrmultiview/makeMultiView.cpp
--- openexr-3.4.6+ds/src/bin/exrmultiview/makeMultiView.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/bin/exrmultiview/makeMultiView.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -107,6 +107,14 @@
             string         inChanName = j.name ();
             string outChanName = insertViewName (inChanName, viewNames, i);
 
+            if (inChannel.xSampling != 1 || inChannel.ySampling != 1)
+                THROW (IEX_NAMESPACE::ArgExc,
+                       "no support for subsampling:"
+                       << " channel " << inChanName
+                       << " of " << inFileNames[i]
+                       << " has subsampling " << inChannel.xSampling
+                       << ", " << inChannel.ySampling);
+            
             image.addChannel (outChanName, inChannel);
             image.channel (outChanName).black ();
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfAcesFile.h openexr-3.4.14/src/lib/OpenEXR/ImfAcesFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfAcesFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfAcesFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -43,6 +43,8 @@
 //	converted to CIE XYZ, a color adaptation transform shifts the
 //	white point, and the result is converted to ACES RGB.
 //
+//	String parameters that name a file to open are UTF-8 paths; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImathBox.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfArray.h openexr-3.4.14/src/lib/OpenEXR/ImfArray.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfArray.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfArray.h	2026-08-05 04:29:55.000000000 +0300
@@ -7,6 +7,12 @@
 #define INCLUDED_IMF_ARRAY_H
 
 #include "ImfForward.h"
+#include "IexBaseExc.h"
+#include "IexMathExc.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
 
 //-------------------------------------------------------------------------
 //
@@ -54,8 +60,11 @@
         _data = 0;
         _size = 0;
     }
-    Array (long size)
+    Array (long size) : _size (0), _data (0)
     {
+        if (size < 0)
+            throw IEX_NAMESPACE::ArgExc ("Array size must be non-negative");
+
         _data = new T[size];
         _size = size;
     }
@@ -154,10 +163,28 @@
 // Implementation
 //---------------
 
+namespace {
+
+inline size_t
+array2DElementCount (long sizeX, long sizeY)
+{
+    const uint64_t x       = static_cast<uint64_t> (sizeX);
+    const uint64_t y       = static_cast<uint64_t> (sizeY);
+    const uint64_t maxSize = std::numeric_limits<size_t>::max ();
+    if (y > 0 && x > maxSize / y)
+        throw IEX_NAMESPACE::OverflowExc ("Array2D dimensions too large");
+    return static_cast<size_t> (x * y);
+}
+
+} // namespace
+
 template <class T>
 inline void
 Array<T>::resizeErase (long size)
 {
+    if (size < 0)
+        throw IEX_NAMESPACE::ArgExc ("Array size must be non-negative");
+
     T* tmp = new T[size];
     delete[] _data;
     _size = size;
@@ -168,6 +195,9 @@
 inline void
 Array<T>::resizeEraseUnsafe (long size)
 {
+    if (size < 0)
+        throw IEX_NAMESPACE::ArgExc ("Array size must be non-negative");
+
     delete[] _data;
     _data = 0;
     _size = 0;
@@ -183,9 +213,14 @@
 
 template <class T>
 inline Array2D<T>::Array2D (long sizeX, long sizeY)
-    : _sizeX (sizeX), _sizeY (sizeY), _data (new T[sizeX * sizeY])
+    : _sizeX (0), _sizeY (0), _data (0)
 {
-    // empty
+    if (sizeX < 0 || sizeY < 0)
+        throw IEX_NAMESPACE::ArgExc ("Array2D dimensions must be non-negative");
+
+    _sizeX = sizeX;
+    _sizeY = sizeY;
+    _data  = new T[array2DElementCount (sizeX, sizeY)];
 }
 
 template <class T> inline Array2D<T>::~Array2D ()
@@ -211,7 +246,10 @@
 inline void
 Array2D<T>::resizeErase (long sizeX, long sizeY)
 {
-    T* tmp = new T[sizeX * sizeY];
+    if (sizeX < 0 || sizeY < 0)
+        throw IEX_NAMESPACE::ArgExc ("Array2D dimensions must be non-negative");
+
+    T* tmp = new T[array2DElementCount (sizeX, sizeY)];
     delete[] _data;
     _sizeX = sizeX;
     _sizeY = sizeY;
@@ -222,11 +260,14 @@
 inline void
 Array2D<T>::resizeEraseUnsafe (long sizeX, long sizeY)
 {
+    if (sizeX < 0 || sizeY < 0)
+        throw IEX_NAMESPACE::ArgExc ("Array2D dimensions must be non-negative");
+
     delete[] _data;
     _data  = 0;
     _sizeX = 0;
     _sizeY = 0;
-    _data  = new T[sizeX * sizeY];
+    _data  = new T[array2DElementCount (sizeX, sizeY)];
     _sizeX = sizeX;
     _sizeY = sizeY;
 }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfCompression.cpp openexr-3.4.14/src/lib/OpenEXR/ImfCompression.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfCompression.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfCompression.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -180,13 +180,13 @@
         "htj2k256",
         "High-Throughput JPEG 2000 (256 lines)",
         256,
-        true,
+        false,
         false),
     CompressionDesc (
         "htj2k32",
         "High-Throughput JPEG 2000 (32 lines)",
         32,
-        true,
+        false,
         false),
 };
 // clang-format on
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfContext.h openexr-3.4.14/src/lib/OpenEXR/ImfContext.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfContext.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfContext.h	2026-08-05 04:29:55.000000000 +0300
@@ -27,6 +27,8 @@
 /// the main entrypoint for querying any of the metadata for all the
 /// component parts of the file, along with the global header
 /// information.
+///
+/// Constructors that take a filename expect a UTF-8 encoded path; see ImfIO.h.
 class IMF_EXPORT_TYPE Context
 {
 public:
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfCRgbaFile.h openexr-3.4.14/src/lib/OpenEXR/ImfCRgbaFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfCRgbaFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfCRgbaFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -15,6 +15,11 @@
 #endif
 
 /*
+** File names passed to ImfOpen* functions are UTF-8 encoded (see ImfIO.h in the
+** C++ API).
+*/
+
+/*
 ** Interpreting unsigned shorts as 16-bit floating point numbers
 */
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp openexr-3.4.14/src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -617,6 +617,21 @@
     std::vector<DeepSlice> fills;
     ScanLineProcess proc;
 
+    if (countsOnly)
+    {
+        const Slice& scslice = fb.getSampleCountSlice ();
+        if (!scslice.base)
+        {
+            throw IEX_NAMESPACE::ArgExc (
+                "Invalid base pointer, please set a proper sample count slice.");
+        }
+        if (scslice.type != OPENEXR_IMF_INTERNAL_NAMESPACE::UINT)
+        {
+            throw IEX_NAMESPACE::ArgExc (
+                "The type of sample count slice should be UINT.");
+        }
+    }
+
     if (!countsOnly)
         prepFillList(fb, fills);
 
@@ -986,6 +1001,17 @@
 {
     const Slice& scslice = outfb->getSampleCountSlice ();
 
+    if (!scslice.base)
+    {
+        throw IEX_NAMESPACE::ArgExc (
+            "Invalid base pointer, please set a proper sample count slice.");
+    }
+    if (scslice.type != OPENEXR_IMF_INTERNAL_NAMESPACE::UINT)
+    {
+        throw IEX_NAMESPACE::ArgExc (
+            "The type of sample count slice should be UINT.");
+    }
+
     int     end = cinfo.height - decoder.user_line_end_ignore;
     int64_t xS = int64_t (scslice.xStride);
     int64_t yS = int64_t (scslice.yStride);
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepScanLineInputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfDeepScanLineInputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepScanLineInputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfDeepScanLineInputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //      class DeepScanLineInputFile
 //
+//      Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepScanLineOutputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfDeepScanLineOutputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepScanLineOutputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfDeepScanLineOutputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //      class DeepScanLineOutputFile
 //
+//      Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfExport.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepTiledInputFile.cpp openexr-3.4.14/src/lib/OpenEXR/ImfDeepTiledInputFile.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepTiledInputFile.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfDeepTiledInputFile.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -279,6 +279,18 @@
                        "subsampling factors.");
     }
 
+    const Slice& sampleCountSlice = frameBuffer.getSampleCountSlice ();
+    if (!sampleCountSlice.base)
+    {
+        throw IEX_NAMESPACE::ArgExc (
+            "Invalid base pointer, please set a proper sample count slice.");
+    }
+    if (sampleCountSlice.type != UINT)
+    {
+        throw IEX_NAMESPACE::ArgExc (
+            "The type of sample count slice should be UINT.");
+    }
+
     _data->frameBuffer = frameBuffer;
     _data->frameBufferValid = true;
 }
@@ -1199,6 +1211,14 @@
     if (s.xSampling != 1 || s.ySampling != 1)
         throw IEX_NAMESPACE::ArgExc ("Tiled data should not have subsampling.");
 
+    if (s.base == nullptr)
+        throw IEX_NAMESPACE::ArgExc (
+            "Deep frame buffer is missing sample counts; call insertSampleCountSlice before reading.");
+
+    if (s.type != UINT)
+        throw IEX_NAMESPACE::ArgExc (
+            "The type of sample count slice should be UINT.");
+
     int xOffset = s.xTileCoords ? 0 : t_absX;
     int yOffset = s.yTileCoords ? 0 : t_absY;
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepTiledInputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfDeepTiledInputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepTiledInputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfDeepTiledInputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //      class DeepTiledInputFile
 //
+//      Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepTiledOutputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfDeepTiledOutputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfDeepTiledOutputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfDeepTiledOutputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //      class DeepTiledOutputFile
 //
+//      Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfIDManifest.cpp openexr-3.4.14/src/lib/OpenEXR/ImfIDManifest.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfIDManifest.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfIDManifest.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -115,6 +115,13 @@
             throw IEX_NAMESPACE::InputExc (
                 "IDManifest too small for variable length integer");
         }
+        // Each chunk contributes at most 7 bits; shifts must stay < 64 or
+        // (byte & 127) << shift has undefined behavior (C++).
+        if (shift >= 64)
+        {
+            throw IEX_NAMESPACE::InputExc (
+                "Invalid variable-length integer in IDManifest");
+        }
         byte = *(unsigned char*) readPtr++;
         // top bit of byte isn't part of actual number, it just indicates there's more info to come
         // so take bottom 7 bits, shift them to the right place, and insert them
@@ -170,21 +177,53 @@
         Xdr::read<CharPtrIO> (readPtr, numberOfStrings);
     }
 
-    vector<size_t> lengths (numberOfStrings);
+
+    if (numberOfStrings < 0)
+    {
+        throw IEX_NAMESPACE::InputExc (
+            "Negative count for number of strings");
+    }
+
+    if (readPtr + numberOfStrings > endPtr)
+    {
+        throw IEX_NAMESPACE::InputExc (
+            "IDManifest too small for string length table");
+    }
+
+
+    //
+    // compute total table size
+    //
+    const char* tablePtr = readPtr;
+
+    size_t totalTableSize = 0;
 
     for (int i = 0; i < numberOfStrings; ++i)
     {
-        lengths[i] = readVariableLengthInteger (readPtr, endPtr);
+        totalTableSize += readVariableLengthInteger (readPtr, endPtr);
+    }
+
+
+    if(readPtr + totalTableSize > endPtr)
+    {
+        throw IEX_NAMESPACE::InputExc ("IDManifest too small for string table");
     }
+
+    //
+    // now tablePtr points to size of string in string table, and readPtr
+    // points to the string itself
+    //
+
     for (int i = 0; i < numberOfStrings; ++i)
     {
 
-        if (readPtr + lengths[i] > endPtr)
+        size_t length = readVariableLengthInteger (tablePtr, endPtr);
+        if (readPtr + length > endPtr)
         {
             throw IEX_NAMESPACE::InputExc ("IDManifest too small for string");
         }
-        outputVector.insert (outputVector.end (), string (readPtr, lengths[i]));
-        readPtr += lengths[i];
+        outputVector.insert (outputVector.end (), string (readPtr, length));
+        readPtr += length;
     }
 }
 
@@ -337,6 +376,13 @@
         //
         // previous string had more than 255 characters?
         //
+        const size_t minPrefixLen =
+            stringList[i - 1].size () > 255 ? size_t (2) : size_t (1);
+        if (stringList[i].size () < minPrefixLen)
+        {
+            throw IEX_NAMESPACE::InputExc (
+                "IDManifest string too small for common prefix length");
+        }
         if (stringList[i - 1].size () > 255)
         {
             common = size_t (((unsigned char) (stringList[i][0])) << 8) +
@@ -515,7 +561,7 @@
             for (size_t i = 0; i < m.getComponents ().size (); ++i)
             {
                 int stringIndex = readVariableLengthInteger (data, endOfData);
-                if (size_t (stringIndex) > stringList.size () ||
+                if (size_t (stringIndex) >= stringList.size () ||
                     stringIndex < 0)
                 {
                     throw IEX_NAMESPACE::InputExc (
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfInputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfInputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfInputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfInputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -11,6 +11,8 @@
 //	class InputFile -- a scanline-based interface that can be used
 //	to read both scanline-based and tiled OpenEXR image files.
 //
+//	Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfInputPart.cpp openexr-3.4.14/src/lib/OpenEXR/ImfInputPart.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfInputPart.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfInputPart.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -55,7 +55,21 @@
 bool
 InputPart::isOptimizationEnabled () const
 {
+#if defined(_MSC_VER)
+    __pragma(warning(push))
+    __pragma(warning(disable: 4996))
+#elif defined(__clang__) || defined(__GNUC__)
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
     return file->isOptimizationEnabled ();
+
+#if defined(_MSC_VER)
+    __pragma(warning(pop))
+#elif defined(__clang__) || defined(__GNUC__)
+    #pragma GCC diagnostic pop
+#endif
 }
 
 void
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfInputPart.h openexr-3.4.14/src/lib/OpenEXR/ImfInputPart.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfInputPart.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfInputPart.h	2026-08-05 04:29:55.000000000 +0300
@@ -34,6 +34,7 @@
     const FrameBuffer& frameBuffer () const;
     IMF_EXPORT
     bool isComplete () const;
+    OPENEXR_DEPRECATED ("No longer meaningful")
     IMF_EXPORT
     bool isOptimizationEnabled () const;
     IMF_EXPORT
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfIO.h openexr-3.4.14/src/lib/OpenEXR/ImfIO.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfIO.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfIO.h	2026-08-05 04:29:55.000000000 +0300
@@ -12,6 +12,16 @@
 //
 //-----------------------------------------------------------------------------
 
+//
+// File path encoding: Any const char* string that names or opens a file,
+// including the strings passed to the protected IStream(const char*) and
+// OStream(const char*) constructors, and the value returned by fileName(),
+// is interpreted as UTF-8 on all platforms, including Microsoft
+// Windows. Callers must not pass legacy ANSI/code-page paths. This matches
+// StdIFStream/StdOFStream (via std::filesystem::u8path) and the OpenEXRCore
+// C API.
+//
+
 #include "ImfForward.h"
 
 #include <cstdint>
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfMisc.h openexr-3.4.14/src/lib/OpenEXR/ImfMisc.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfMisc.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfMisc.h	2026-08-05 04:29:55.000000000 +0300
@@ -435,10 +435,10 @@
 int getChunkOffsetTableSize (const Header& header);
 
 //
-// Convert a filename to a wide string.  This is useful for working with
-// filenames on Windows.
+// Convert a UTF-8 filename to a wide string (for example for Windows APIs).
 //
 
+OPENEXR_DEPRECATED ("To be removed in future releases.")
 IMF_EXPORT
 std::wstring WidenFilename (const char* filename);
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfMultiPartInputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfMultiPartInputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfMultiPartInputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfMultiPartInputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -14,6 +14,10 @@
 
 OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER
 
+//
+// Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
+
 /// \brief
 ///
 /// TODO: Document this
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfMultiPartOutputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfMultiPartOutputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfMultiPartOutputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfMultiPartOutputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -32,6 +32,8 @@
 //                             to copy the values over from the first header.
 //  numThreads - number of threads that should be used in encoding the data.
 //
+// Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 
 class IMF_EXPORT_TYPE MultiPartOutputFile : public GenericOutputFile
 {
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfMultiView.cpp openexr-3.4.14/src/lib/OpenEXR/ImfMultiView.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfMultiView.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfMultiView.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -118,7 +118,7 @@
         // in the name belong to the default view.
         //
 
-        return multiView[0];
+        return defaultViewName (multiView);
     }
     else
     {
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfName.h openexr-3.4.14/src/lib/OpenEXR/ImfName.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfName.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfName.h	2026-08-05 04:29:55.000000000 +0300
@@ -72,6 +72,7 @@
 Name::operator= (const char text[])
 {
     strncpy (_text, text, MAX_LENGTH);
+    _text[MAX_LENGTH] = 0;
     return *this;
 }
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfOutputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfOutputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfOutputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfOutputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //	class OutputFile
 //
+//	Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfRgbaFile.cpp openexr-3.4.14/src/lib/OpenEXR/ImfRgbaFile.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfRgbaFile.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfRgbaFile.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -90,7 +90,11 @@
 {
     if (layerName.empty ()) return "";
 
-    if (hasMultiView (header) && multiView (header)[0] == layerName) return "";
+    if (hasMultiView (header))
+    {
+        StringVector sv = multiView (header);
+        if (!sv.empty() && sv[0] == layerName) return "";
+    }
 
     return layerName + ".";
 }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfRgbaFile.h openexr-3.4.14/src/lib/OpenEXR/ImfRgbaFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfRgbaFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfRgbaFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -13,6 +13,8 @@
 //	class RgbaOutputFile
 //	class RgbaInputFile
 //
+//	Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfExport.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfScanLineInputFile.cpp openexr-3.4.14/src/lib/OpenEXR/ImfScanLineInputFile.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfScanLineInputFile.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfScanLineInputFile.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -361,7 +361,8 @@
     if (EXR_ERR_SUCCESS == exr_read_scanline_chunk_info (
                                _ctxt, _data->partNumber, scanLine, &cinfo))
     {
-        if (cinfo.packed_size > static_cast<uint64_t> (pixelDataSize))
+        if (pixelDataSize < 0 ||
+            cinfo.packed_size > static_cast<uint64_t> (pixelDataSize))
         {
             THROW (
                 IEX_NAMESPACE::ArgExc,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfScanLineInputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfScanLineInputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfScanLineInputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfScanLineInputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //	class ScanLineInputFile
 //
+//	Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
@@ -110,6 +112,7 @@
     //
     //---------------------------------------------------------------
 
+    OPENEXR_DEPRECATED ("No longer meaningful")
     IMF_EXPORT
     bool isOptimizationEnabled () const;
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfSimd.h openexr-3.4.14/src/lib/OpenEXR/ImfSimd.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfSimd.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfSimd.h	2026-08-05 04:29:55.000000000 +0300
@@ -13,11 +13,11 @@
 //
 
 // GCC and Visual Studio SSE2 compiler flags
-#if defined __SSE2__ || (_MSC_VER && (_M_IX86 || _M_X64))
+#if defined __SSE2__ || (_MSC_VER && (_M_IX86 || _M_X64) && !defined(_M_ARM64EC))
 #    define IMF_HAVE_SSE2 1
 #endif
 
-#if defined __SSE4_1__ || (_MSC_VER && (_M_IX86 || _M_X64))
+#if defined __SSE4_1__ || (_MSC_VER && (_M_IX86 || _M_X64) && !defined(_M_ARM64EC))
 #    define IMF_HAVE_SSE4_1 1
 #endif
 
@@ -42,7 +42,7 @@
 #    define IMF_HAVE_F16C 1
 #endif
 
-#if defined(__ARM_NEON)
+#if defined(__ARM_NEON) || defined(_M_ARM64) || defined(_M_ARM64EC)
 #    define IMF_HAVE_NEON
 #endif
 
@@ -50,6 +50,14 @@
 #    define IMF_HAVE_NEON_AARCH64 1
 #endif
 
+#if defined(_M_ARM64) || defined(_M_ARM64EC)
+#    define IMF_HAVE_NEON_WINDOWS_ARM64 1
+#endif
+
+#if defined(IMF_HAVE_NEON_AARCH64) || defined(IMF_HAVE_NEON_WINDOWS_ARM64)
+#    define IMF_HAVE_NEON_ARM64 1
+#endif
+
 extern "C" {
 #ifdef IMF_HAVE_SSE2
 #    include <emmintrin.h>
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfStandardAttributes.cpp openexr-3.4.14/src/lib/OpenEXR/ImfStandardAttributes.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfStandardAttributes.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfStandardAttributes.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -111,13 +111,27 @@
 IMF_STD_ATTRIBUTE_IMP (chromaticities, Chromaticities, Chromaticities)
 IMF_STD_ATTRIBUTE_IMP (whiteLuminance, WhiteLuminance, float)
 IMF_STD_ATTRIBUTE_IMP (adoptedNeutral, AdoptedNeutral, V2f)
+
+#if defined(_MSC_VER)
+    __pragma(warning(push))
+    __pragma(warning(disable: 4996))
+#elif defined(__clang__) || defined(__GNUC__)
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
 IMF_STD_ATTRIBUTE_IMP (renderingTransform, RenderingTransform, string)
 IMF_STD_ATTRIBUTE_IMP (lookModTransform, LookModTransform, string)
+IMF_STD_ATTRIBUTE_IMP (dwaCompressionLevel, DwaCompressionLevel, float)
+#if defined(_MSC_VER)
+    __pragma(warning(pop))
+#elif defined(__clang__) || defined(__GNUC__)
+    #pragma GCC diagnostic pop
+#endif
+
 IMF_STD_ATTRIBUTE_IMP (envmap, Envmap, Envmap)
 IMF_STD_ATTRIBUTE_IMP (wrapmodes, Wrapmodes, string)
 IMF_STD_ATTRIBUTE_IMP (multiView, MultiView, StringVector)
 IMF_STD_ATTRIBUTE_IMP (deepImageState, DeepImageState, DeepImageState)
-IMF_STD_ATTRIBUTE_IMP (dwaCompressionLevel, DwaCompressionLevel, float)
 IMF_STD_ATTRIBUTE_IMP (idManifest, IDManifest, CompressedIDManifest)
 IMF_STD_ATTRIBUTE_IMP (colorInteropID, ColorInteropID, string)
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfStdIO.h openexr-3.4.14/src/lib/OpenEXR/ImfStdIO.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfStdIO.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfStdIO.h	2026-08-05 04:29:55.000000000 +0300
@@ -13,6 +13,10 @@
 //
 //-----------------------------------------------------------------------------
 
+//
+// Constructors taking const char fileName[] expect a UTF-8 path; see ImfIO.h.
+//
+
 #include "ImfExport.h"
 #include "ImfNamespace.h"
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfTestFile.h openexr-3.4.14/src/lib/OpenEXR/ImfTestFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfTestFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfTestFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -18,6 +18,10 @@
 
 OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER
 
+//
+// Filename parameters are UTF-8 encoded (including on Windows). See ImfIO.h.
+//
+
 IMF_EXPORT bool isOpenExrFile (const char fileName[]);
 
 IMF_EXPORT bool isOpenExrFile (const char fileName[], bool& isTiled);
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledInputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfTiledInputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledInputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfTiledInputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //	class TiledInputFile
 //
+//	Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledMisc.cpp openexr-3.4.14/src/lib/OpenEXR/ImfTiledMisc.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledMisc.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfTiledMisc.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -378,7 +378,9 @@
                     "Bad level mode getting chunk offset table size");
         }
         delete[] numXTiles;
+        numXTiles = nullptr;
         delete[] numYTiles;
+        numYTiles = nullptr;
 
         return static_cast<int> (lineOffsetSize);
     }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledOutputFile.h openexr-3.4.14/src/lib/OpenEXR/ImfTiledOutputFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledOutputFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfTiledOutputFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,8 @@
 //
 //	class TiledOutputFile
 //
+//	Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledRgbaFile.cpp openexr-3.4.14/src/lib/OpenEXR/ImfTiledRgbaFile.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledRgbaFile.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfTiledRgbaFile.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -94,7 +94,11 @@
 {
     if (layerName.empty ()) return "";
 
-    if (hasMultiView (header) && multiView (header)[0] == layerName) return "";
+    if (hasMultiView (header))
+    {
+        StringVector sv = multiView (header);
+        if (!sv.empty() && sv[0] == layerName) return "";
+    }
 
     return layerName + ".";
 }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledRgbaFile.h openexr-3.4.14/src/lib/OpenEXR/ImfTiledRgbaFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfTiledRgbaFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfTiledRgbaFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -13,6 +13,8 @@
 //	class TiledRgbaOutputFile
 //	class TiledRgbaInputFile
 //
+//	Constructors that open a file by path expect UTF-8; see ImfIO.h.
+//
 //-----------------------------------------------------------------------------
 
 #include "ImfForward.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXR/ImfZip.cpp openexr-3.4.14/src/lib/OpenEXR/ImfZip.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXR/ImfZip.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXR/ImfZip.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -157,7 +157,7 @@
 
 #endif
 
-#ifdef IMF_HAVE_NEON_AARCH64
+#ifdef IMF_HAVE_NEON_ARM64
 
 void
 reconstruct_neon (char* buf, size_t outSize)
@@ -258,7 +258,7 @@
 
 #endif
 
-#ifdef IMF_HAVE_NEON_AARCH64
+#ifdef IMF_HAVE_NEON_ARM64
 
 void
 interleave_neon (const char* source, size_t outSize, char* out)
@@ -370,7 +370,7 @@
     if (cpuId.sse2) { interleave = interleave_sse2; }
 #endif
 
-#ifdef IMF_HAVE_NEON_AARCH64
+#ifdef IMF_HAVE_NEON_ARM64
     reconstruct = reconstruct_neon;
     interleave  = interleave_neon;
 #endif
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/bytes.c openexr-3.4.14/src/lib/OpenEXRCore/bytes.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/bytes.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/bytes.c	2026-08-05 04:29:55.000000000 +0300
@@ -76,11 +76,25 @@
     const void* t,
     const void* d)
 {
+    if (h > 0 && !t)
+        return ctxt->print_error (
+            ctxt,
+            EXR_ERR_INVALID_ARGUMENT,
+            "Invalid NULL type hint for bytes attribute with hint length %u",
+            h);
+
+    if (b > 0 && !d)
+        return ctxt->print_error (
+            ctxt,
+            EXR_ERR_INVALID_ARGUMENT,
+            "Invalid NULL bytes data for bytes attribute with size %zu",
+            b);
+
     exr_result_t rv = exr_attr_bytes_init (ctxt, u, h, b);
     if (rv == EXR_ERR_SUCCESS)
     {
-        if (d && u->data) memcpy ((void*) u->data, d, b);
-        if (d && u->type_hint) memcpy ((void*) u->type_hint, t, h);
+        if (b > 0 && u->data) memcpy ((void*) u->data, d, b);
+        if (h > 0 && u->type_hint) memcpy ((void*) u->type_hint, t, h);
     }
 
     return rv;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/channel_list.c openexr-3.4.14/src/lib/OpenEXRCore/channel_list.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/channel_list.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/channel_list.c	2026-08-05 04:29:55.000000000 +0300
@@ -221,6 +221,13 @@
     if (!chl || !srcchl) return EXR_ERR_INVALID_ARGUMENT;
 
     numchans = srcchl->num_channels;
+    if (numchans > 0 && !srcchl->entries)
+        return ctxt->print_error (
+            ctxt,
+            EXR_ERR_INVALID_ARGUMENT,
+            "Invalid NULL channel list entries with %d channels",
+            numchans);
+
     rv       = exr_attr_chlist_init (ctxt, chl, numchans);
     if (rv != EXR_ERR_SUCCESS) return rv;
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/chunk.c openexr-3.4.14/src/lib/OpenEXRCore/chunk.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/chunk.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/chunk.c	2026-08-05 04:29:55.000000000 +0300
@@ -516,10 +516,11 @@
     EXR_LOCK_WRITE_AND_DEFINE_PART (part_index);
 
     if (!chunk_offset_out)
-        return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT);
+        return EXR_UNLOCK_WRITE_AND_RETURN (
+            ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT));
 
     *chunk_offset_out = part->chunk_table_offset;
-    return EXR_ERR_SUCCESS;
+    return EXR_UNLOCK_WRITE_AND_RETURN (EXR_ERR_SUCCESS);
 }
 
 exr_result_t
@@ -1005,7 +1006,7 @@
         if (rv != EXR_ERR_SUCCESS) { return rv; }
         priv_to_native64 (ddata, 3);
 
-        if (ddata[0] < 0)
+        if (ddata[0] < 0 || ddata[0] > (int64_t) INT_MAX)
         {
             return ctxt->print_error (
                 ctxt,
@@ -1119,6 +1120,17 @@
     if (cinfo->packed_size == 0 && cinfo->unpacked_size > 0)
         return ctxt->report_error (
             ctxt, EXR_ERR_INVALID_ARGUMENT, "Invalid packed size of 0");
+
+    if (part->comp_type == EXR_COMPRESSION_NONE &&
+        cinfo->packed_size != cinfo->unpacked_size)
+    {
+        return ctxt->print_error (
+            ctxt,
+            EXR_ERR_BAD_CHUNK_LEADER,
+            "Mismatch between unpacked and packed size with uncompressed data: packed is %" PRIu64 "; unpacked is %" PRIu64,
+            cinfo->packed_size, cinfo->unpacked_size);
+    }
+
     return EXR_ERR_SUCCESS;
 }
 
@@ -1639,6 +1651,13 @@
 
     if (rv != EXR_ERR_SUCCESS) return rv;
 
+    /* the on-disk sample count table is little-endian; convert to native so
+     * callers receive host-order counts (matching unpacked pixel data) */
+    if (sample_data && cinfo->sample_count_table_size > 0)
+        priv_to_native32 (
+            sample_data,
+            (int) (cinfo->sample_count_table_size / sizeof (uint32_t)));
+
     if (packed_data && cinfo->packed_size > 0)
     {
         dataoffset = cinfo->data_offset;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/CMakeLists.txt openexr-3.4.14/src/lib/OpenEXRCore/CMakeLists.txt
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/CMakeLists.txt	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/CMakeLists.txt	2026-08-05 04:29:55.000000000 +0300
@@ -153,10 +153,12 @@
 
 else()
 
-  # Vendored OpenJPH
-  # Note that the vendored OpenJPH cmake config forces a static build
+  # Vendored OpenJPH (bundled into OpenEXRCore; not installed or exported)
 
+  set(OJPH_INSTALL OFF CACHE BOOL "Do not install bundled OpenJPH" FORCE)
   set(OJPH_BUILD_EXECUTABLES OFF CACHE BOOL "Disable OpenJPH command-line tools" FORCE)
+  set(OJPH_LIB_TYPE OBJECT CACHE STRING "Bundled OpenJPH library type" FORCE)
+
   set(OPENJPH_SOURCE_DIR ${PROJECT_SOURCE_DIR}/external/OpenJPH)
   set(OPENJPH_BINARY_DIR ${PROJECT_BINARY_DIR}/external/OpenJPH)
   add_subdirectory(${OPENJPH_SOURCE_DIR}
@@ -168,8 +170,14 @@
     CXX_VISIBILITY_PRESET hidden
     VISIBILITY_INLINES_HIDDEN YES
   )
-  target_link_libraries(OpenEXRCore PRIVATE openjph)
   target_include_directories(OpenEXRCore PRIVATE ${OPENJPH_SOURCE_DIR}/src/core)
 
+  target_sources(OpenEXRCore PRIVATE $<TARGET_OBJECTS:openjph>)
+  get_target_property(_openexr_ojph_iface_defs openjph INTERFACE_COMPILE_DEFINITIONS)
+  if (_openexr_ojph_iface_defs)
+    target_compile_definitions(OpenEXRCore PRIVATE ${_openexr_ojph_iface_defs})
+  endif()
+  unset(_openexr_ojph_iface_defs)
+
 endif()
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/compression.c openexr-3.4.14/src/lib/OpenEXRCore/compression.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/compression.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/compression.c	2026-08-05 04:29:55.000000000 +0300
@@ -201,7 +201,11 @@
         }
         else if (res == LIBDEFLATE_SHORT_OUTPUT)
         {
-            /* TODO: is this an error? */
+            /* Decompression succeeded; *actual_out is the byte count. This is
+             * not an error when out_bytes_avail exceeds the true uncompressed
+             * size (e.g. PXR24/ZIP use padded scratch buffers). Callers that
+             * need an exact payload size must compare *actual_out (see e.g.
+             * undo_pxr24_impl). */
             return EXR_ERR_SUCCESS;
         }
         return EXR_ERR_CORRUPT_CHUNK;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/decoding.c openexr-3.4.14/src/lib/OpenEXRCore/decoding.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/decoding.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/decoding.c	2026-08-05 04:29:55.000000000 +0300
@@ -26,12 +26,14 @@
     if (stortype == EXR_STORAGE_DEEP_SCANLINE ||
         stortype == EXR_STORAGE_DEEP_TILED)
     {
-        size_t sampsize =
-            (((size_t) decode->chunk.width) * ((size_t) decode->chunk.height));
+        uint64_t sampsize64 =
+            ((uint64_t) decode->chunk.width) * ((uint64_t) decode->chunk.height);
 
         if ((decode->decode_flags & EXR_DECODE_SAMPLE_COUNTS_AS_INDIVIDUAL))
-            sampsize += 1;
-        sampsize *= sizeof (int32_t);
+            sampsize64 += 1;
+        sampsize64 *= sizeof (int32_t);
+        if (sampsize64 != (size_t) sampsize64) return EXR_ERR_OUT_OF_MEMORY;
+        size_t sampsize = (size_t) sampsize64;
 
         if (decode->chunk.sample_count_table_size == sampsize)
         {
@@ -72,12 +74,14 @@
     }
     else
     {
+        if (decode->chunk.unpacked_size != (size_t) decode->chunk.unpacked_size)
+            return EXR_ERR_OUT_OF_MEMORY;
         rv = internal_decode_alloc_buffer (
             decode,
             EXR_TRANSCODE_BUFFER_UNPACKED,
             &(decode->unpacked_buffer),
             &(decode->unpacked_alloc_size),
-            decode->chunk.unpacked_size);
+            (size_t) decode->chunk.unpacked_size);
     }
 
     return rv;
@@ -215,8 +219,8 @@
 unpack_sample_table (exr_const_context_t ctxt, exr_decode_pipeline_t* decode)
 {
     exr_result_t rv           = EXR_ERR_SUCCESS;
-    int32_t      w            = decode->chunk.width;
-    int32_t      h            = decode->chunk.height;
+    int64_t      w            = decode->chunk.width;
+    int64_t      h            = decode->chunk.height;
     uint64_t     totsamp      = 0;
     int32_t*     samptable    = decode->sample_count_table;
     size_t       combSampSize = 0;
@@ -226,7 +230,7 @@
 
     if ((decode->decode_flags & EXR_DECODE_SAMPLE_COUNTS_AS_INDIVIDUAL))
     {
-        for (int32_t y = 0; y < h; ++y)
+        for (int64_t y = 0; y < h; ++y)
         {
             int32_t* cursampline = samptable + y * w;
             int32_t  prevsamp    = 0;
@@ -246,7 +250,7 @@
     }
     else
     {
-        for (int32_t y = 0; y < h; ++y)
+        for (int64_t y = 0; y < h; ++y)
         {
             int32_t* cursampline = samptable + y * w;
             int32_t  prevsamp    = 0;
@@ -586,7 +590,25 @@
         (part->storage_mode == EXR_STORAGE_DEEP_SCANLINE ||
          part->storage_mode == EXR_STORAGE_DEEP_TILED))
     {
-        if (part->comp_type == EXR_COMPRESSION_NONE &&
+        if (part->comp_type != EXR_COMPRESSION_NONE &&
+            decode->packed_sample_count_table == NULL &&
+            decode->sample_count_table != NULL)
+        {
+            /* A compressed deep chunk declared a zero-length (absent)
+             * packed sample-count table, so decompress_data() above was
+             * never invoked and decode->sample_count_table, which was
+             * allocated to hold the full decompressed table, was left
+             * uninitialized. Treat an absent table as all-zero sample
+             * counts instead of reading uninitialized heap memory in
+             * unpack_sample_table().
+             */
+            memset (
+                decode->sample_count_table,
+                0,
+                decode->sample_count_alloc_size);
+        }
+        else if (
+            part->comp_type == EXR_COMPRESSION_NONE &&
             decode->sample_count_table != decode->packed_sample_count_table)
         {
             /* Check that uncompressed data can be copied in safely.
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/encoding.c openexr-3.4.14/src/lib/OpenEXRCore/encoding.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/encoding.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/encoding.c	2026-08-05 04:29:55.000000000 +0300
@@ -319,9 +319,13 @@
          part->storage_mode == EXR_STORAGE_DEEP_TILED) &&
         encode->sample_count_table != NULL)
     {
-        priv_from_native32 (
-            encode->sample_count_table,
-            encode->chunk.width * encode->chunk.height);
+        /* Process the sample count table line by line to avoid integer overflows */
+        int32_t* current_line = encode->sample_count_table;
+        for (int y = 0; y < encode->chunk.height; ++y)
+        {
+            priv_from_native32 (current_line, encode->chunk.width);
+            current_line += encode->chunk.width;
+        }
     }
 
     if (rv == EXR_ERR_SUCCESS)
@@ -366,9 +370,13 @@
          part->storage_mode == EXR_STORAGE_DEEP_TILED) &&
         encode->sample_count_table != NULL)
     {
-        priv_to_native32 (
-            encode->sample_count_table,
-            encode->chunk.width * encode->chunk.height);
+        /* Process the sample count table line by line to avoid integer overflows */
+        int32_t* current_line = encode->sample_count_table;
+        for (int y = 0; y < encode->chunk.height; ++y)
+        {
+            priv_to_native32 (current_line, encode->chunk.width);
+            current_line += encode->chunk.width;
+        }
     }
 
     return rv;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_b44.c openexr-3.4.14/src/lib/OpenEXRCore/internal_b44.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_b44.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_b44.c	2026-08-05 04:29:55.000000000 +0300
@@ -427,13 +427,13 @@
             // rightmost column and the bottom row.
             //
             uint16_t *row0, *row1, *row2, *row3;
+            /* row offset in elements: use uint64_t so y*nx cannot overflow int */
+            uint64_t row_off = (uint64_t) (y) * (uint64_t) (nx);
 
-            row0 = (uint16_t*) scratch;
-            row0 += y * nx;
-
-            row1 = row0 + nx;
-            row2 = row1 + nx;
-            row3 = row2 + nx;
+            row0 = (uint16_t*) scratch + row_off;
+            row1 = row0 + (uint64_t) nx;
+            row2 = row1 + (uint64_t) nx;
+            row3 = row2 + (uint64_t) nx;
 
             if (y + 3 >= ny)
             {
@@ -557,11 +557,12 @@
 
         for (int y = 0; y < ny; y += 4)
         {
-            row0 = (uint16_t*) scratch;
-            row0 += y * nx;
-            row1 = row0 + nx;
-            row2 = row1 + nx;
-            row3 = row2 + nx;
+            /* row offset in elements: use uint64_t so y*nx cannot overflow int */
+            uint64_t row_off = (uint64_t) (y) * (uint64_t) (nx);
+            row0 = (uint16_t*) scratch + row_off;
+            row1 = row0 + (uint64_t) nx;
+            row2 = row1 + (uint64_t) nx;
+            row3 = row2 + (uint64_t) nx;
             for (int x = 0; x < nx; x += 4)
             {
                 if (bIn + 3 > comp_buf_size) return EXR_ERR_OUT_OF_MEMORY;
@@ -693,12 +694,14 @@
     uint64_t               uncompressed_size)
 {
     exr_result_t rv;
+    uint64_t     scratch_sz = compute_scratch_buffer_size (decode, uncompressed_size);
+    if (scratch_sz != (size_t) scratch_sz) return EXR_ERR_OUT_OF_MEMORY;
     rv = internal_decode_alloc_buffer (
         decode,
         EXR_TRANSCODE_BUFFER_SCRATCH1,
         &(decode->scratch_buffer_1),
         &(decode->scratch_alloc_size_1),
-        compute_scratch_buffer_size (decode, uncompressed_size));
+        (size_t) scratch_sz);
     if (rv != EXR_ERR_SUCCESS) return rv;
 
     exrcore_ensure_b44_tables ();
@@ -720,14 +723,18 @@
     uint64_t               uncompressed_size)
 {
     exr_result_t rv;
+    uint64_t     scratch_sz = compute_scratch_buffer_size (decode, uncompressed_size);
+    if (scratch_sz != (size_t) scratch_sz) return EXR_ERR_OUT_OF_MEMORY;
     rv = internal_decode_alloc_buffer (
         decode,
         EXR_TRANSCODE_BUFFER_SCRATCH1,
         &(decode->scratch_buffer_1),
         &(decode->scratch_alloc_size_1),
-        compute_scratch_buffer_size (decode, uncompressed_size));
+        (size_t) scratch_sz);
     if (rv != EXR_ERR_SUCCESS) return rv;
 
+    exrcore_ensure_b44_tables ();
+
     return uncompress_b44_impl (
         decode,
         compressed_data,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_cpuid.h openexr-3.4.14/src/lib/OpenEXRCore/internal_cpuid.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_cpuid.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_cpuid.h	2026-08-05 04:29:55.000000000 +0300
@@ -9,7 +9,7 @@
 #include "OpenEXRConfigInternal.h"
 
 #if defined(i386) || defined(__i386__) || defined(__i386) ||                   \
-    defined(_M_X86) || defined(__x86_64__) || defined(_M_X64)
+    defined(_M_X86) || defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
 #    define OPENEXR_ENABLE_X86_SIMD_CHECK 1
 #else
 #    define OPENEXR_ENABLE_X86_SIMD_CHECK 0
@@ -132,7 +132,7 @@
     int sse2, avx, f16c;
     check_for_x86_simd (&f16c, &avx, &sse2);
     return avx && f16c;
-#elif defined(__aarch64__)
+#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)
     return 1;
 #else
     return 0;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_compressor.h openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_compressor.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_compressor.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_compressor.h	2026-08-05 04:29:55.000000000 +0300
@@ -688,6 +688,43 @@
 
 /**************************************/
 
+static exr_result_t
+validate_size(DwaCompressor* me, CompressorScheme compression,
+              uint64_t compressedSize, uint64_t uncompressedSize,
+              uint64_t extraSize)
+{
+    uint64_t requiredSize = 0;
+    for (int c = 0; c < me->_numChannels; ++c)
+    {
+        if (me->_channelData[c].compression == compression)
+        {
+            uint64_t chanSize = (uint64_t) me->_channelData[c].planarUncSize;
+
+            /* guard against wraparound when accumulating attacker-influenced
+             * per-channel sizes; a wrap would understate requiredSize and
+             * defeat the validation below */
+            if (chanSize > UINT64_MAX - requiredSize)
+                return EXR_ERR_CORRUPT_CHUNK;
+
+            requiredSize += chanSize;
+        }
+    }
+
+    if (requiredSize > 0)
+    {
+        if (uncompressedSize < requiredSize || compressedSize == 0)
+        {
+            return EXR_ERR_CORRUPT_CHUNK;
+        }
+    }
+    else if (uncompressedSize > 0 || compressedSize > 0 || extraSize > 0)
+    {
+        return EXR_ERR_CORRUPT_CHUNK;
+    }
+
+    return EXR_ERR_SUCCESS;
+}
+
 exr_result_t
 DwaCompressor_uncompress (
     DwaCompressor* me,
@@ -769,6 +806,16 @@
         return EXR_ERR_CORRUPT_CHUNK;
     }
 
+    /* check for overflow conditions in the unc sizes, corrupt file no
+       need to check the rleUncompressedSize, the zipped rle data will
+       be checked below */
+    if (unknownUncompressedSize > uncompressed_size ||
+        rleRawSize > uncompressed_size ||
+        (unknownUncompressedSize + rleRawSize) > uncompressed_size)
+    {
+        return EXR_ERR_CORRUPT_CHUNK;
+    }
+
     if ((int64_t) unknownUncompressedSize < 0 ||
         (int64_t) unknownCompressedSize < 0 || (int64_t) acCompressedSize < 0 ||
         (int64_t) dcCompressedSize < 0 || (int64_t) rleCompressedSize < 0 ||
@@ -857,6 +904,12 @@
     rv = DwaCompressor_setupChannelData (me);
     if (rv != EXR_ERR_SUCCESS) { return rv; }
 
+    rv = validate_size(me, UNKNOWN, unknownCompressedSize, unknownUncompressedSize, 0);
+    if (rv != EXR_ERR_SUCCESS) { return rv; }
+    
+    rv = validate_size(me, RLE, rleCompressedSize, rleRawSize, rleUncompressedSize);
+    if (rv != EXR_ERR_SUCCESS) { return rv; }
+    
     //
     // Uncompress the UNKNOWN data into _planarUncBuffer[UNKNOWN]
     //
@@ -1037,7 +1090,8 @@
             if (rv != EXR_ERR_SUCCESS) return rv;
 
             cd->_dctData._type = chan->data_type;
-            outBufferEnd += chan->width * chan->bytes_per_element;
+            outBufferEnd +=
+                (size_t) chan->width * (size_t) chan->bytes_per_element;
         }
     }
 
@@ -1392,10 +1446,15 @@
     if (maxLossyDctAcSize * numLossyDctChans > me->_packedAcBufferSize)
     {
         me->_packedAcBufferSize = maxLossyDctAcSize * numLossyDctChans;
+        if (me->_packedAcBufferSize > SIZE_MAX)
+        {
+            return EXR_ERR_OUT_OF_MEMORY;
+        }
+
         if (me->_packedAcBuffer != NULL) me->free_fn (me->_packedAcBuffer);
-        me->_packedAcBuffer = me->alloc_fn (me->_packedAcBufferSize);
+        me->_packedAcBuffer = me->alloc_fn ((size_t) me->_packedAcBufferSize);
         if (!me->_packedAcBuffer) return EXR_ERR_OUT_OF_MEMORY;
-        memset (me->_packedAcBuffer, 0, me->_packedAcBufferSize);
+        memset (me->_packedAcBuffer, 0, (size_t) me->_packedAcBufferSize);
     }
 
     //
@@ -1405,19 +1464,29 @@
     if (maxLossyDctDcSize * numLossyDctChans > me->_packedDcBufferSize)
     {
         me->_packedDcBufferSize = maxLossyDctDcSize * numLossyDctChans;
+        if (me->_packedDcBufferSize > SIZE_MAX)
+        {
+            return EXR_ERR_OUT_OF_MEMORY;
+        }
+
         if (me->_packedDcBuffer != NULL) me->free_fn (me->_packedDcBuffer);
-        me->_packedDcBuffer = me->alloc_fn (me->_packedDcBufferSize);
+        me->_packedDcBuffer = me->alloc_fn ((size_t) me->_packedDcBufferSize);
         if (!me->_packedDcBuffer) return EXR_ERR_OUT_OF_MEMORY;
-        memset (me->_packedDcBuffer, 0, me->_packedDcBufferSize);
+        memset (me->_packedDcBuffer, 0, (size_t) me->_packedDcBufferSize);
     }
 
     if (rleBufferSize > me->_rleBufferSize)
     {
         me->_rleBufferSize = rleBufferSize;
+        if (rleBufferSize > SIZE_MAX)
+        {
+            return EXR_ERR_OUT_OF_MEMORY;
+        }
+
         if (me->_rleBuffer != 0) me->free_fn (me->_rleBuffer);
-        me->_rleBuffer = me->alloc_fn (rleBufferSize);
+        me->_rleBuffer = me->alloc_fn ((size_t) rleBufferSize);
         if (!me->_rleBuffer) return EXR_ERR_OUT_OF_MEMORY;
-        memset (me->_rleBuffer, 0, rleBufferSize);
+        memset (me->_rleBuffer, 0, (size_t) rleBufferSize);
     }
 
     //
@@ -1718,8 +1787,8 @@
         {
             for (int byte = 1; byte < curc->bytes_per_element; ++byte)
             {
-                cd->planarUncRle[byte] =
-                    cd->planarUncRle[byte - 1] + curc->width * curc->height;
+                cd->planarUncRle[byte] = cd->planarUncRle[byte - 1] +
+                    (size_t) curc->width * (size_t) curc->height;
 
                 cd->planarUncRleEnd[byte] = cd->planarUncRle[byte];
             }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_decoder.h openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_decoder.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_decoder.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_decoder.h	2026-08-05 04:29:55.000000000 +0300
@@ -328,7 +328,7 @@
     rowBlock[0] = (uint16_t*) simd_align_pointer (rowBlockHandle);
 
     for (int comp = 1; comp < numComp; ++comp)
-        rowBlock[comp] = rowBlock[comp - 1] + numBlocksX * 64;
+        rowBlock[comp] = rowBlock[comp - 1] + (size_t) numBlocksX * 64;
 
     //
     // Pack DC components together by common plane, so we can get
@@ -338,7 +338,7 @@
 
     currDcComp[0] = (uint16_t*) d->_packedDc;
     for (int comp = 1; comp < numComp; ++comp)
-        currDcComp[comp] = currDcComp[comp - 1] + numBlocksX * numBlocksY;
+        currDcComp[comp] = currDcComp[comp - 1] + (size_t) numBlocksX * numBlocksY;
 
     for (int blocky = 0; blocky < numBlocksY; ++blocky)
     {
@@ -741,13 +741,22 @@
         /* process in place in reverse to avoid temporary buffer */
         for (int y = 0; y < d->_height; ++y)
         {
-            float*    floatXdrPtr = (float*) chanData[chan]->_rows[y];
-            uint16_t* halfXdr     = (uint16_t*) floatXdrPtr;
+            uint8_t* rowBytes = chanData[chan]->_rows[y];
 
             for (int x = d->_width - 1; x >= 0; --x)
             {
-                floatXdrPtr[x] = one_from_native_float (
-                    half_to_float (one_to_native16 (halfXdr[x])));
+                // TODO: make an unaligned_store32f that takes the float and
+                // packages up a one_from_native_float and calls memcpy
+                // instead of the two memcpy. We should look at the metrics
+                // for dwa and see if there's a performance difference to do
+                // so at some point. See:
+                // https://github.com/AcademySoftwareFoundation/openexr/pull/2324
+
+                uint16_t h = unaligned_load16 (rowBytes + x * sizeof (uint16_t));
+                float    f = half_to_float (h);
+                uint32_t bits;
+                memcpy (&bits, &f, sizeof (bits));
+                unaligned_store32 (rowBytes + x * sizeof (float), bits);
             }
         }
     }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_encoder.h openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_encoder.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_encoder.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_encoder.h	2026-08-05 04:29:55.000000000 +0300
@@ -968,7 +968,7 @@
     uint16_t halfZigCoef[64];
 
     uint16_t* currAcComp            = (uint16_t*) e->_packedAc;
-    int       tmpHalfBufferElements = 0;
+    size_t    tmpHalfBufferElements = 0;
     uint16_t* tmpHalfBuffer         = NULL;
     uint16_t* tmpHalfBufferPtr      = NULL;
 
@@ -984,13 +984,13 @@
     {
         chanData[chan] = e->_channel_encode_data[chan];
         if (chanData[chan]->_type == EXR_PIXEL_FLOAT)
-            tmpHalfBufferElements += e->_width * e->_height;
+            tmpHalfBufferElements += (size_t) e->_width * (size_t) e->_height;
     }
 
     if (tmpHalfBufferElements)
     {
-        tmpHalfBuffer = (uint16_t*) alloc_fn (
-            (size_t) tmpHalfBufferElements * sizeof (uint16_t));
+        tmpHalfBuffer =
+            (uint16_t*) alloc_fn (tmpHalfBufferElements * sizeof (uint16_t));
         if (!tmpHalfBuffer) return EXR_ERR_OUT_OF_MEMORY;
         tmpHalfBufferPtr = tmpHalfBuffer;
     }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_simd.h openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_simd.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_dwa_simd.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_dwa_simd.h	2026-08-05 04:29:55.000000000 +0300
@@ -18,7 +18,7 @@
 // aligned. Unaligned pointers may risk seg-faulting.
 //
 
-#if defined __SSE2__ || (_MSC_VER && (_M_IX86 || _M_X64))
+#if defined __SSE2__ || (_MSC_VER && (_M_IX86 || _M_X64) && !defined(_M_ARM64EC))
 #    define IMF_HAVE_SSE2 1
 #    include <emmintrin.h>
 #    include <mmintrin.h>
@@ -35,6 +35,22 @@
 #    define IMF_HAVE_NEON_AARCH64 1
 #endif
 
+#if defined(_M_ARM64) || defined(_M_ARM64EC)
+#    define IMF_HAVE_NEON_WINDOWS_ARM64 1
+#endif
+
+#if defined(IMF_HAVE_NEON_AARCH64) || defined(IMF_HAVE_NEON_WINDOWS_ARM64)
+#    define IMF_HAVE_NEON_ARM64 1
+#endif
+
+#if defined(IMF_HAVE_NEON_ARM64)
+#    if defined(_MSC_VER)
+#        define NEON_RESTRICT __restrict
+#    else
+#        define NEON_RESTRICT __restrict__
+#    endif
+#endif
+
 #include "internal_coding.h"
 
 #if defined(OPENEXR_IMF_HAVE_GCC_INLINE_ASM_AVX) &&                            \
@@ -357,7 +373,7 @@
         dst[i] = float_to_half (src[i]);
 }
 
-#ifdef IMF_HAVE_NEON_AARCH64
+#ifdef IMF_HAVE_NEON_ARM64
 
 void
 convertFloatToHalf64_neon (uint16_t* dst, float* src)
@@ -786,36 +802,50 @@
 #endif /* defined IMF_HAVE_GCC_INLINEASM_X86_64 */
 }
 
-#ifdef IMF_HAVE_NEON_AARCH64
+#ifdef IMF_HAVE_NEON_ARM64
 
 void
-fromHalfZigZag_neon (uint16_t* __restrict__ src, float* __restrict__ dst)
+fromHalfZigZag_neon (uint16_t* NEON_RESTRICT src, float* NEON_RESTRICT dst)
 {
+#    if defined(_MSC_VER)
+    static const uint8_t res_tbl_data[4][16] = {
+        {0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42},
+        {3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53},
+        {10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60},
+        {21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63}};
+
+    uint8x16_t res_tbl[4];
+    for (int i = 0; i < 4; i++)
+    {
+        res_tbl[i] = vld1q_u8 (res_tbl_data[i]);
+    }
+
+#    else
     uint8x16_t res_tbl[4] = {
         {0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42},
         {3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53},
         {10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60},
         {21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63}};
+#    endif
 
     uint8x16x4_t vec_input_l, vec_input_h;
-
     for (int i = 0; i < 4; i++)
     {
         uint8x16x2_t vec_in_u8 = vld2q_u8 ((uint8_t*) (src + 16 * i));
         vec_input_l.val[i]     = vec_in_u8.val[0];
         vec_input_h.val[i]     = vec_in_u8.val[1];
     }
-
 #    pragma unroll(4)
     for (int i = 0; i < 4; i++)
     {
-        uint8x16_t res_vec_l, res_vec_h;
-        res_vec_l = vqtbl4q_u8 (vec_input_l, res_tbl[i]);
-        res_vec_h = vqtbl4q_u8 (vec_input_h, res_tbl[i]);
+        uint8x16_t res_vec_l = vqtbl4q_u8 (vec_input_l, res_tbl[i]);
+        uint8x16_t res_vec_h = vqtbl4q_u8 (vec_input_h, res_tbl[i]);
+
         float16x8_t res_vec_l_f16 =
             vreinterpretq_f16_u8 (vzip1q_u8 (res_vec_l, res_vec_h));
         float16x8_t res_vec_h_f16 =
             vreinterpretq_f16_u8 (vzip2q_u8 (res_vec_l, res_vec_h));
+
         vst1q_f32 (dst + i * 16, vcvt_f32_f16 (vget_low_f16 (res_vec_l_f16)));
         vst1q_f32 (dst + i * 16 + 4, vcvt_high_f32_f16 (res_vec_l_f16));
         vst1q_f32 (
@@ -824,7 +854,7 @@
     }
 }
 
-#endif // IMF_HAVE_NEON_AARCH64
+#endif //IMF_HAVE_NEON_ARM64
 
 //
 // Inverse 8x8 DCT, only inverting the DC. This assumes that
@@ -2315,7 +2345,7 @@
     if (done) return;
     done = 1;
 
-#ifdef IMF_HAVE_NEON_AARCH64
+#ifdef IMF_HAVE_NEON_ARM64
     {
         convertFloatToHalf64 = convertFloatToHalf64_neon;
         fromHalfZigZag       = fromHalfZigZag_neon;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_ht_common.cpp openexr-3.4.14/src/lib/OpenEXRCore/internal_ht_common.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_ht_common.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_ht_common.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -215,6 +215,11 @@
         return (v << 8) + *cur++;
     }
 
+    size_t tell() const
+    {
+        return this->cur - this->buffer;
+    }
+
 protected:
     uint8_t* buffer;
     uint8_t* cur;
@@ -291,20 +296,25 @@
     MemoryReader header ((uint8_t*) buffer, max_sz);
     if (header.pull_uint16 () != HEADER_MARKER)
         throw std::runtime_error (
-            "HTJ2K chunk header missing does not start with magic number.");
+            "HTJ2K chunk header does not start with magic number.");
 
-    size_t length = header.pull_uint32 ();
+    size_t payload_sz = header.pull_uint32 ();
+    size_t prefix_sz = header.tell ();
 
-    if (length < 2)
-        throw std::runtime_error ("Error while reading the channel map");
-
-    length += HEADER_SZ;
+    if (payload_sz + prefix_sz > max_sz)
+        throw std::runtime_error (
+            "HTJ2K chunk header length is larger than the chunk size.");
 
-    map.resize (header.pull_uint16 ());
+    map.resize (header.pull_uint16 (), {-1, 0, 0});
     for (size_t i = 0; i < map.size (); i++)
     {
-        map.at (i).file_index = header.pull_uint16 ();
+        uint16_t file_index = header.pull_uint16 ();
+        if (file_index >= map.size() || map.at (file_index).scratch > 0)
+            throw std::runtime_error (
+                "HTJ2K chunk header contains invalid file_index values.");
+        map.at (i).file_index = file_index;
+        map.at (file_index).scratch = 1;
     }
 
-    return length;
+    return prefix_sz + payload_sz;
 }
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_ht_common.h openexr-3.4.14/src/lib/OpenEXRCore/internal_ht_common.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_ht_common.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_ht_common.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,22 +10,76 @@
 #include <stdlib.h>
 #include "openexr_coding.h"
 
+/** Maps a JPEG 2000 codestream component index to the corresponding OpenEXR
+ *  file channel index and its byte offset within a packed raster line. */
 struct CodestreamChannelInfo
 {
     int    file_index;
     size_t raster_line_offset;
+    int    scratch; // used when reading from a file
 };
 
+/** Build a codestream-to-file channel map for @p channel_count OpenEXR
+ *  channels.
+ *
+ *  When the channels include a matching RGB triplet (e.g. R/G/B, Red/Green/Blue,
+ *  or layer-prefixed variants), the first three entries of @p cs_to_file_ch are
+ *  assigned to R, G, and B respectively so that the JPEG 2000 Reversible Color
+ *  Transform (RCT) can be applied by the encoder.  All remaining channels follow
+ *  in their original order.  When no RGB triplet is detected the channels are
+ *  mapped in their original order.
+ *
+ *  @param channel_count  Number of channels in @p channels.
+ *  @param channels       OpenEXR per-channel coding descriptors.
+ *  @param cs_to_file_ch  Output map from J2K component index to file channel.
+ *                        Resized to @p channel_count on return.
+ *  @return true  if an RGB triplet was detected (RCT will be applied),
+ *          false otherwise.
+ */
 bool make_channel_map (
     int                                 channel_count,
     exr_coding_channel_info_t*          channels,
     std::vector<CodestreamChannelInfo>& cs_to_file_ch);
 
+/** Write an HTJ2K chunk header into @p buffer.
+ *
+ *  The header encodes the channel map so that a decoder can reconstruct the
+ *  original OpenEXR channel ordering from the JPEG 2000 codestream.  It has
+ *  the following on-disk layout (all integers big-endian):
+ *  @code
+ *    uint16_t  MAGIC  = 0x4854 ('H','T')
+ *    uint32_t  PLEN          // payload length in bytes
+ *    uint16_t  NCH           // number of entries in the channel map
+ *    uint16_t  CS_TO_F[NCH]  // OpenEXR channel index for each J2K component
+ *    // optional opaque extension bytes up to PLEN
+ *    // JPEG 2000 codestream follows immediately after the header
+ *  @endcode
+ *
+ *  @param buffer   Destination buffer; must be at least @p max_sz bytes.
+ *  @param max_sz   Capacity of @p buffer in bytes.
+ *  @param map      Channel map produced by make_channel_map().
+ *  @return Number of bytes written; the JPEG 2000 codestream should be
+ *          placed at this offset within @p buffer.
+ */
 size_t write_header (
     uint8_t*                                  buffer,
     size_t                                    max_sz,
     const std::vector<CodestreamChannelInfo>& map);
 
+/** Parse an HTJ2K chunk header from @p buffer and populate the channel map.
+ *
+ *  Validates the magic number, reads the payload length, and decodes the
+ *  per-component OpenEXR channel indices.  Extension bytes inside the payload
+ *  (beyond the channel map) are silently skipped.
+ *
+ *  @param buffer   Chunk data; must be at least @p max_sz bytes.
+ *  @param max_sz   Number of readable bytes starting at @p buffer.
+ *  @param map      Populated with one entry per J2K component on success.
+ *  @return Byte offset of the JPEG 2000 codestream within @p buffer, i.e. the
+ *          total size of the header including its payload.
+ *  @throws std::runtime_error if the magic number is absent, the header is
+ *          larger than @p max_sz.
+ */
 size_t read_header (
     void*                               buffer,
     size_t                              max_sz,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_ht.cpp openexr-3.4.14/src/lib/OpenEXRCore/internal_ht.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_ht.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_ht.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -172,8 +172,25 @@
     /* read the channel map */
 
     size_t header_sz;
-    header_sz = read_header (
-        (uint8_t*) compressed_data, comp_buf_size, cs_to_file_ch);
+    try
+    {
+        header_sz = read_header (
+            (uint8_t*) compressed_data, comp_buf_size, cs_to_file_ch);
+    }
+    catch (...)
+    {
+        return EXR_ERR_CORRUPT_CHUNK;
+    }
+
+    /* this should never be true since read_header() throws an exception if the
+    header is larger than comp_buf_size */
+    if (header_sz > comp_buf_size)
+        return EXR_ERR_CORRUPT_CHUNK;
+
+    const uint64_t codestream_sz = comp_buf_size - header_sz;
+    if (codestream_sz == 0)
+        return EXR_ERR_CORRUPT_CHUNK;
+
     if (static_cast<std::size_t>(decode->channel_count) != cs_to_file_ch.size ())
         return EXR_ERR_CORRUPT_CHUNK;
 
@@ -183,17 +200,19 @@
         if (file_i >= decode->channel_count)
             return EXR_ERR_CORRUPT_CHUNK;
 
-        size_t computedoffset = 0;
+        int64_t computedoffset = 0;
         for (int i = 0; i < file_i; ++i)
-            computedoffset += decode->channels[i].width *
-                              decode->channels[i].bytes_per_element;
+            computedoffset += (int64_t) decode->channels[i].width *
+                              (int64_t) decode->channels[i].bytes_per_element;
+        if (computedoffset > std::numeric_limits<std::size_t>::max())
+            return EXR_ERR_CORRUPT_CHUNK;
         cs_to_file_ch[cs_i].raster_line_offset = computedoffset;
     }
 
     ojph::mem_infile infile;
     infile.open (
         reinterpret_cast<const ojph::ui8*> (compressed_data) + header_sz,
-        comp_buf_size - header_sz);
+        codestream_sz);
 
     ojph::codestream cs;
     cs.read_headers (&infile);
@@ -202,22 +221,37 @@
 
     ojph::ui32 image_height =
         siz.get_image_extent ().y - siz.get_image_offset ().y;
+    ojph::ui32 image_width =
+        siz.get_image_extent ().x - siz.get_image_offset ().x;
 
-    if (decode->chunk.width != siz.get_image_extent ().x - siz.get_image_offset ().x
+    if (decode->chunk.width != image_width
         || decode->chunk.height != image_height
         || decode->channel_count != siz.get_num_components())
         return EXR_ERR_CORRUPT_CHUNK;
 
-    int  bpl       = 0;
-    bool is_planar = false;
+    for (int cs_i = 0; cs_i < decode->channel_count; cs_i++)
+    {
+        int file_i = cs_to_file_ch[cs_i].file_index;
+
+        if (decode->channels[file_i].height != siz.get_recon_height (cs_i) ||
+            decode->channels[file_i].width != siz.get_recon_width (cs_i) ||
+            decode->channels[file_i].height != image_height / siz.get_downsampling (cs_i).y ||
+            decode->channels[file_i].width != image_width / siz.get_downsampling (cs_i).x)
+            return EXR_ERR_CORRUPT_CHUNK;
+    }
+
+    int64_t bpl     = 0;
+    bool is_planar  = false;
     for (int16_t c = 0; c < decode->channel_count; c++)
     {
-        bpl +=
-            decode->channels[c].bytes_per_element * decode->channels[c].width;
+        bpl += (int64_t) decode->channels[c].bytes_per_element *
+               (int64_t) decode->channels[c].width;
         if (decode->channels[c].x_samples > 1 ||
             decode->channels[c].y_samples > 1)
         { is_planar = true; }
     }
+    if (bpl > INT32_MAX || bpl * decode->chunk.height > (int64_t) PTRDIFF_MAX)
+        return EXR_ERR_CORRUPT_CHUNK;
     cs.set_planar (is_planar);
 
     cs.create ();
@@ -231,17 +265,21 @@
         for (int16_t c = 0; c < decode->channel_count; c++)
         {
             int file_c = cs_to_file_ch[c].file_index;
-            assert (
-                siz.get_recon_height (c) == decode->channels[file_c].height);
-            assert (decode->channels[file_c].width == siz.get_recon_width (c));
 
             if (decode->channels[file_c].height == 0) continue;
 
             uint8_t* line_pixels = static_cast<uint8_t*> (uncompressed_data);
 
-            for (int64_t y = decode->chunk.start_y;
-                 y < image_height + decode->chunk.start_y;
-                 y++)
+            /* image_height is an unsigned OpenJPH type; compute the loop
+             * bounds in a signed 64-bit type so that a negative start_y
+             * (from a negative data-window Y origin) does not get
+             * promoted to unsigned and wrap the end condition to a huge
+             * positive value, which would cause the loop to run far past
+             * the declared image data (CWE-190 / CWE-835). */
+            const int64_t start_y = static_cast<int64_t> (decode->chunk.start_y);
+            const int64_t end_y   = start_y + static_cast<int64_t> (image_height);
+
+            for (int64_t y = start_y; y < end_y; y++)
             {
                 for (int16_t line_c = 0; line_c < decode->channel_count;
                      line_c++)
@@ -257,7 +295,7 @@
                             EXR_PIXEL_HALF)
                         {
                             int16_t* channel_pixels = (int16_t*) line_pixels;
-                            for (int16_t p = 0;
+                            for (int32_t p = 0;
                                  p < decode->channels[file_c].width;
                                  p++)
                             {
@@ -267,7 +305,7 @@
                         else
                         {
                             int32_t* channel_pixels = (int32_t*) line_pixels;
-                            for (int16_t p = 0;
+                            for (int32_t p = 0;
                                  p < decode->channels[file_c].width;
                                  p++)
                             {
@@ -276,8 +314,8 @@
                         }
                     }
 
-                    line_pixels += decode->channels[line_c].bytes_per_element *
-                                   decode->channels[line_c].width;
+                    line_pixels += (size_t) decode->channels[line_c].bytes_per_element *
+                                   (size_t) decode->channels[line_c].width;
                 }
             }
         }
@@ -299,7 +337,7 @@
                 {
                     int16_t* channel_pixels =
                         (int16_t*) (line_pixels + cs_to_file_ch[c].raster_line_offset);
-                    for (int16_t p = 0; p < decode->channels[file_c].width;
+                    for (int32_t p = 0; p < decode->channels[file_c].width;
                          p++)
                     {
                         *channel_pixels++ = cur_line->i32[p];
@@ -309,7 +347,7 @@
                 {
                     int32_t* channel_pixels =
                         (int32_t*) (line_pixels + cs_to_file_ch[c].raster_line_offset);
-                    for (int16_t p = 0; p < decode->channels[file_c].width;
+                    for (int32_t p = 0; p < decode->channels[file_c].width;
                          p++)
                     {
                         *channel_pixels++ = cur_line->i32[p];
@@ -368,7 +406,7 @@
 
     bool isPlanar = false;
     siz.set_num_components (encode->channel_count);
-    int bpl = 0;
+    int64_t bpl = 0;
     for (int16_t c = 0; c < encode->channel_count; c++)
     {
         int file_c = cs_to_file_ch[c].file_index;
@@ -391,7 +429,8 @@
         bpl += encode->channels[file_c].bytes_per_element *
                encode->channels[file_c].width;
     }
-
+    if (bpl > INT32_MAX || bpl * encode->chunk.height > (int64_t) PTRDIFF_MAX)
+        return EXR_ERR_CORRUPT_CHUNK;
     cs.set_planar (isPlanar);
 
     siz.set_image_offset (ojph::point (0, 0));
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_piz.c openexr-3.4.14/src/lib/OpenEXRCore/internal_piz.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_piz.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_piz.c	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,7 @@
 #include "internal_huf.h"
 #include "internal_xdr.h"
 
+#include <limits.h>
 #include <string.h>
 
 /**************************************/
@@ -232,10 +233,11 @@
 static void
 wav_2D_encode (uint16_t* in, int nx, int ox, int ny, int oy, uint16_t mx)
 {
-    int w14 = (mx < (1 << 14)) ? 1 : 0;
-    int n   = (nx > ny) ? ny : nx;
-    int p   = 1; // == 1 <<  level
-    int p2  = 2; // == 1 << (level+1)
+    int     w14  = (mx < (1 << 14)) ? 1 : 0;
+    int     n    = (nx > ny) ? ny : nx;
+    int     p    = 1; // == 1 <<  level
+    int     p2   = 2; // == 1 << (level+1)
+    int64_t oy64 = oy;
 
     //
     // Hierarchical loop on smaller dimension n
@@ -244,9 +246,9 @@
     while (p2 <= n)
     {
         uint16_t* py  = in;
-        uint16_t* ey  = in + oy * (ny - p2);
-        int       oy1 = oy * p;
-        int       oy2 = oy * p2;
+        uint16_t* ey  = in + oy64 * (ny - p2);
+        int64_t   oy1 = oy64 * p;
+        int64_t   oy2 = oy64 * p2;
         int       ox1 = ox * p;
         int       ox2 = ox * p2;
         uint16_t  i00, i01, i10, i11;
@@ -345,10 +347,11 @@
     int       oy, // i : y offset
     uint16_t  mx)  // i : maximum in[x][y] value
 {
-    int w14 = (mx < (1 << 14)) ? 1 : 0;
-    int n   = (nx > ny) ? ny : nx;
-    int p   = 1;
-    int p2;
+    int     w14  = (mx < (1 << 14)) ? 1 : 0;
+    int     n    = (nx > ny) ? ny : nx;
+    int     p    = 1;
+    int     p2;
+    int64_t oy64 = oy;
 
     //
     // Search max level
@@ -368,9 +371,9 @@
     while (p >= 1)
     {
         uint16_t* py  = in;
-        uint16_t* ey  = in + oy * (ny - p2);
-        int       oy1 = oy * p;
-        int       oy2 = oy * p2;
+        uint16_t* ey  = in + oy64 * (ny - p2);
+        int64_t   oy1 = oy64 * p;
+        int64_t   oy2 = oy64 * p2;
         int       ox1 = ox * p;
         int       ox2 = ox * p2;
         uint16_t  i00, i01, i10, i11;
@@ -566,11 +569,13 @@
         nx     = curc->width;
         ny     = curc->height;
         wcount = (int) (curc->bytes_per_element / 2);
+        if (wcount > 0 && nx > INT_MAX / wcount)
+            return EXR_ERR_CORRUPT_CHUNK;
         for (int j = 0; j < wcount; ++j)
         {
             wav_2D_encode (wavbuf + j, nx, wcount, ny, wcount * nx, maxValue);
         }
-        wavbuf += nx * ny * wcount;
+        wavbuf += (uint64_t) nx * ny * wcount;
     }
 
     nBytes    = 0;
@@ -722,11 +727,13 @@
         nx     = curc->width;
         ny     = curc->height;
         wcount = (int) (curc->bytes_per_element / 2);
+        if (wcount > 0 && nx > INT_MAX / wcount)
+            return EXR_ERR_CORRUPT_CHUNK;
         for (int j = 0; j < wcount; ++j)
         {
             wav_2D_decode (wavbuf + j, nx, wcount, ny, wcount * nx, maxValue);
         }
-        wavbuf += nx * ny * wcount;
+        wavbuf += (uint64_t) nx * ny * wcount;
     }
 
     //
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_pxr24.c openexr-3.4.14/src/lib/OpenEXRCore/internal_pxr24.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_pxr24.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_pxr24.c	2026-08-05 04:29:55.000000000 +0300
@@ -182,7 +182,7 @@
                     if (nOut + nBytes > encode->scratch_alloc_size_1)
                         return EXR_ERR_OUT_OF_MEMORY;
                     nOut += nBytes;
-                    lastIn += w * 4;
+                    lastIn += (uint64_t) w * 4;
 
                     ptr[0] = out;
                     out += w;
@@ -320,7 +320,7 @@
                     ptr[3] = lastIn;
                     lastIn += w;
 
-                    if (nDec + nBytes > uncompressed_size)
+                    if (nDec + nBytes > outSize)
                         return EXR_ERR_CORRUPT_CHUNK;
 
                     for (int x = 0; x < w; ++x)
@@ -347,7 +347,7 @@
                     ptr[1] = lastIn;
                     lastIn += w;
 
-                    if (nDec + nBytes > uncompressed_size)
+                    if (nDec + nBytes > outSize)
                         return EXR_ERR_CORRUPT_CHUNK;
 
                     for (int x = 0; x < w; ++x)
@@ -374,7 +374,7 @@
                     ptr[2] = lastIn;
                     lastIn += w;
 
-                    if (nDec + (uint64_t) (w * 3) > uncompressed_size)
+                    if (nDec + (uint64_t) w * 3 > outSize)
                         return EXR_ERR_CORRUPT_CHUNK;
 
                     for (int x = 0; x < w; ++x)
@@ -387,7 +387,7 @@
                         unaligned_store32 (dout, pixel);
                         ++dout;
                     }
-                    nDec += (uint64_t) (w * 3);
+                    nDec += (uint64_t) w * 3;
                     break;
                 }
                 default: return EXR_ERR_INVALID_ARGUMENT;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_structs.c openexr-3.4.14/src/lib/OpenEXRCore/internal_structs.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_structs.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_structs.c	2026-08-05 04:29:55.000000000 +0300
@@ -162,6 +162,10 @@
     atomic_store (&(cur->chunk_table), (uintptr_t) (0));
 #endif
     if (ctable && ((uintptr_t) ctable) != UINTPTR_MAX) dofree (ctable);
+
+    /* we avoid malloc on one part files, but need to check */
+    if (cur != &(ctxt->first_part)) { dofree (cur); }
+    else { memset (cur, 0, sizeof (struct _priv_exr_part_t)); }
 }
 
 /**************************************/
@@ -169,19 +173,11 @@
 static void
 internal_exr_destroy_parts (exr_context_t ctxt)
 {
-    exr_memory_free_func_t dofree = ctxt->free_fn;
     for (int p = 0; p < ctxt->num_parts; ++p)
-    {
-        exr_priv_part_t cur = ctxt->parts[p];
-
-        internal_exr_destroy_part (ctxt, cur);
-
-        /* the first one is always the one that is part of the file */
-        if (cur != &(ctxt->first_part)) { dofree (cur); }
-        else { memset (cur, 0, sizeof (struct _priv_exr_part_t)); }
-    }
+        internal_exr_destroy_part (ctxt, ctxt->parts[p]);
 
-    if (ctxt->num_parts > 1) dofree (ctxt->parts);
+    if (ctxt->parts != &(ctxt->init_part)) ctxt->free_fn (ctxt->parts);
+    ctxt->init_part = NULL;
     ctxt->parts     = NULL;
     ctxt->num_parts = 0;
 }
@@ -200,15 +196,14 @@
 
     if (ncount == 1)
     {
-        /* no need to zilch, the parent struct will have already been zero'ed */
+        /* avoid an initial malloc by embedding a part in the struct... */
         part         = &(f->first_part);
+        /* also need a pointer to pointer... */
         f->init_part = part;
         nptrs        = &(f->init_part);
     }
     else
     {
-        struct _priv_exr_part_t nil = {0};
-
         part = f->alloc_fn (sizeof (struct _priv_exr_part_t));
         if (!part) return f->standard_error (f, EXR_ERR_OUT_OF_MEMORY);
 
@@ -218,8 +213,8 @@
             f->free_fn (part);
             return f->standard_error (f, EXR_ERR_OUT_OF_MEMORY);
         }
-        *part = nil;
     }
+    memset (part, 0, sizeof (struct _priv_exr_part_t));
 
     /* assign appropriately invalid values */
     part->storage_mode         = EXR_STORAGE_LAST_TYPE;
@@ -234,16 +229,13 @@
     part->dwa_compression_level = f->default_dwa_quality;
 
     /* put it into the part table */
-    if (ncount > 1)
+    for (int p = 0; p < f->num_parts; ++p)
     {
-        for (int p = 0; p < f->num_parts; ++p)
-        {
-            nptrs[p] = f->parts[p];
-        }
-        nptrs[ncount - 1] = part;
+        nptrs[p] = f->parts[p];
     }
+    nptrs[f->num_parts] = part;
 
-    if (f->num_parts > 1) { f->free_fn (f->parts); }
+    if (f->parts && f->parts != &(f->init_part)) { f->free_fn (f->parts); }
     f->parts     = nptrs;
     f->num_parts = ncount;
     if (outpart) *outpart = part;
@@ -264,20 +256,8 @@
     *new_index = -1;
 
     internal_exr_destroy_part (ctxt, part);
-    if (ncount == 0)
-    {
-        ctxt->num_parts = 0;
-        ctxt->init_part = NULL;
-        ctxt->parts     = NULL;
-    }
-    else if (ncount == 1)
-    {
-        if (part == &(ctxt->first_part)) ctxt->first_part = *(ctxt->parts[1]);
-        ctxt->init_part = &(ctxt->first_part);
-        ctxt->free_fn (ctxt->parts);
-        ctxt->parts = &(ctxt->init_part);
-    }
-    else
+
+    if ( ncount > 0 )
     {
         int np = 0;
         for (int p = 0; p < ctxt->num_parts; ++p)
@@ -287,6 +267,13 @@
             ++np;
         }
     }
+    else
+    {
+        if (ctxt->parts && ctxt->parts != &(ctxt->init_part))
+            ctxt->free_fn (ctxt->parts);
+
+        ctxt->parts = NULL;
+    }
     ctxt->num_parts = ncount;
 }
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_thread.h openexr-3.4.14/src/lib/OpenEXRCore/internal_thread.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_thread.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_thread.h	2026-08-05 04:29:55.000000000 +0300
@@ -8,10 +8,12 @@
 
 #include "openexr_config.h"
 
-// Thread-safe single initiatization, using InitOnceExecuteOnce on Windows,
-// pthread_once elsewhere, or a simple variable if threading is completely disabled.
 #if ILMTHREAD_THREADING_ENABLED
 #    ifdef _WIN32
+/*
+ * On Windows, use native InitOnceExecuteOnce, which avoids MSVC's
+ * problematic <threads.h>
+ */
 #        include <windows.h>
 #        define ONCE_FLAG_INIT INIT_ONCE_STATIC_INIT
 typedef INIT_ONCE once_flag;
@@ -27,7 +29,17 @@
 {
     InitOnceExecuteOnce (flag, once_init_fn, (PVOID) func, NULL);
 }
+#    elif __has_include(<threads.h>) && !defined(__FreeBSD__)
+/*
+ * On Linux (glibc 2.28+), use standard <threads.h>.
+ * FreeBSD requires -lstdthreads for <threads.h>; use pthread fallback instead.
+ */
+#        include <threads.h>
+
 #    else
+/*
+ * No <threads.h> on macOS and older Linux distros: fall back to pthreads
+ */
 #        include <pthread.h>
 #        define ONCE_FLAG_INIT PTHREAD_ONCE_INIT
 typedef pthread_once_t once_flag;
@@ -36,9 +48,17 @@
 {
     (void) pthread_once (flag, func);
 }
+
 #    endif
-#else
-#    define ONCE_FLAG_INIT 0
+
+#endif /* ILMTHREAD_THREADING_ENABLED */
+
+/*
+ * If threading is disabled, or call_once/ONCE_FLAG_INIT wasn't declared
+ * above, declare a default implementation.
+ */
+#ifndef ONCE_FLAG_INIT
+#  define ONCE_FLAG_INIT 0
 typedef int once_flag;
 static inline void
 call_once (once_flag* flag, void (*func) (void))
@@ -50,4 +70,5 @@
 }
 #endif
 
+
 #endif /* OPENEXR_PRIVATE_THREAD_H */
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_util.h openexr-3.4.14/src/lib/OpenEXRCore/internal_util.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_util.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_util.h	2026-08-05 04:29:55.000000000 +0300
@@ -6,6 +6,7 @@
 #ifndef OPENEXR_PRIVATE_UTIL_H
 #define OPENEXR_PRIVATE_UTIL_H
 
+#include <stdio.h>
 #include <stdint.h>
 
 static inline int
@@ -19,25 +20,27 @@
         nlines = (start_y % y_sampling) == 0 ? 1 : 0;
     else
     {
-        int start, end;
+        int off, tmph;
 
         /* computed the number of times y % ysampling == 0, by
          * computing interval based on first and last time that occurs
          * on the given range
          */
-        start = start_y % y_sampling;
-        if (start != 0)
-            start = start_y + (y_sampling - start);
+        if (start_y < 0)
+        {
+            off = -start_y % y_sampling;
+        }
         else
-            start = start_y;
-
-        end = start_y + height - 1;
-        end -= (end < 0 ? -end : end) % y_sampling;
-
-        if (start > end)
-            nlines = start == start_y ? 1 : 0;
-        else
-            nlines = (end - start) / y_sampling + 1;
+        {
+            off = start_y % y_sampling;
+            if (off != 0)
+                off = (y_sampling - off);
+        }
+
+        tmph = height - off;
+        if (tmph == 0) return 0;
+        --tmph;
+        nlines = tmph / y_sampling + 1;
     }
 
     return nlines;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_zip.c openexr-3.4.14/src/lib/OpenEXRCore/internal_zip.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/internal_zip.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/internal_zip.c	2026-08-05 04:29:55.000000000 +0300
@@ -15,18 +15,33 @@
 
 #include "openexr_compression.h"
 
-#if defined __SSE2__ || (_MSC_VER >= 1300 && (_M_IX86 || _M_X64))
+#if defined __SSE2__ || (_MSC_VER >= 1300 && (_M_IX86 || _M_X64) && !defined(_M_ARM64EC))
 #    define IMF_HAVE_SSE2 1
 #    include <emmintrin.h>
 #    include <mmintrin.h>
 #endif
-#if defined __SSE4_1__ || (_MSC_VER >= 1300 && (_M_IX86 || _M_X64))
+#if defined __SSE4_1__ || (_MSC_VER >= 1300 && (_M_IX86 || _M_X64) && !defined(_M_ARM64EC))
 #    define IMF_HAVE_SSE4_1 1
 #    include <smmintrin.h>
 #endif
 #if defined(__aarch64__)
 #    define IMF_HAVE_NEON_AARCH64 1
-#    include <arm_neon.h>
+#endif
+
+#if defined(_M_ARM64) || defined(_M_ARM64EC)
+#    define IMF_HAVE_NEON_WINDOWS_ARM64 1
+#endif
+
+#if defined(IMF_HAVE_NEON_AARCH64) || defined(IMF_HAVE_NEON_WINDOWS_ARM64)
+#    define IMF_HAVE_NEON_ARM64 1
+#endif
+
+#if defined(IMF_HAVE_NEON_ARM64)
+#    if defined(_MSC_VER)
+#        include <arm64_neon.h>
+#    else
+#        include <arm_neon.h>
+#    endif
 #endif
 
 /**************************************/
@@ -77,7 +92,7 @@
         prev      = d;
     }
 }
-#elif defined(IMF_HAVE_NEON_AARCH64)
+#elif defined(IMF_HAVE_NEON_ARM64)
 static void
 reconstruct (uint8_t* buf, const uint64_t outSize)
 {
@@ -174,7 +189,7 @@
         *(sOut++) = (i % 2 == 0) ? *(t1++) : *(t2++);
 }
 
-#elif defined(IMF_HAVE_NEON_AARCH64)
+#elif defined(IMF_HAVE_NEON_ARM64)
 static void
 interleave (uint8_t* out, const uint8_t* const source, const uint64_t outSize)
 {
@@ -298,7 +313,7 @@
     if (res == EXR_ERR_SUCCESS)
     {
         decode->bytes_decompressed = actual_out_bytes;
-        if (comp_buf_size > actual_out_bytes || actual_out_bytes > uncompressed_size)
+        if (actual_out_bytes != uncompressed_size)
             res = EXR_ERR_CORRUPT_CHUNK;
         else
             internal_zip_reconstruct_bytes (
@@ -374,7 +389,7 @@
 
     if (rv == EXR_ERR_SUCCESS)
     {
-        if (compbufsz > encode->packed_bytes)
+        if (compbufsz >= encode->packed_bytes)
         {
             memcpy (
                 encode->compressed_buffer,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/openexr_version.h openexr-3.4.14/src/lib/OpenEXRCore/openexr_version.h
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/openexr_version.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/openexr_version.h	2026-08-05 04:29:55.000000000 +0300
@@ -10,6 +10,6 @@
 
 #    define OPENEXR_VERSION_MAJOR 3
 #    define OPENEXR_VERSION_MINOR 4
-#    define OPENEXR_VERSION_PATCH 6
+#    define OPENEXR_VERSION_PATCH 14
 
 #endif
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/parse_header.c openexr-3.4.14/src/lib/OpenEXRCore/parse_header.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/parse_header.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/parse_header.c	2026-08-05 04:29:55.000000000 +0300
@@ -557,7 +557,7 @@
                 tname);
         }
 
-        priv_to_native32 (attrdata, n);
+        priv_to_native32 (attrdata->arr, n);
     }
 
     return rv;
@@ -748,7 +748,7 @@
 }
 
 /**************************************/
-#include <assert.h>
+
 static exr_result_t
 extract_attr_bytes(
     exr_context_t ctxt,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/part_attr.c openexr-3.4.14/src/lib/OpenEXRCore/part_attr.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/part_attr.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/part_attr.c	2026-08-05 04:29:55.000000000 +0300
@@ -1333,14 +1333,28 @@
         if (attr->bytes->size == val->size &&
             attr->bytes->hint_length == val->hint_length)
         {
-            memcpy (
-                EXR_CONST_CAST (void*, attr->bytes->type_hint),
-                val->type_hint,
-                val->hint_length);
-            memcpy (
-                EXR_CONST_CAST (void*, attr->bytes->data),
-                val->data,
-                val->size);
+            if (val->hint_length > 0 && !val->type_hint)
+                return EXR_UNLOCK_AND_RETURN (ctxt->print_error (
+                    ctxt,
+                    EXR_ERR_INVALID_ARGUMENT,
+                    "Invalid NULL type hint for setting '%s'",
+                    name));
+            if (val->size > 0 && !val->data)
+                return EXR_UNLOCK_AND_RETURN (ctxt->print_error (
+                    ctxt,
+                    EXR_ERR_INVALID_ARGUMENT,
+                    "Invalid NULL bytes data for setting '%s'",
+                    name));
+            if (val->hint_length > 0)
+                memcpy (
+                    EXR_CONST_CAST (void*, attr->bytes->type_hint),
+                    val->type_hint,
+                    val->hint_length);
+            if (val->size > 0)
+                memcpy (
+                    EXR_CONST_CAST (void*, attr->bytes->data),
+                    val->data,
+                    val->size);
         }
         else if (ctxt->mode != EXR_CONTEXT_WRITE && ctxt->mode != EXR_CONTEXT_TEMPORARY)
         {
@@ -1419,38 +1433,10 @@
     if (rv == EXR_ERR_SUCCESS)
     {
         exr_attr_chlist_t clist;
-        int               numchans;
-
-        if (!channels)
-            return EXR_UNLOCK_AND_RETURN (ctxt->report_error (
-                ctxt,
-                EXR_ERR_INVALID_ARGUMENT,
-                "No channels provided for channel list"));
 
-        numchans = channels->num_channels;
-        rv       = exr_attr_chlist_init (ctxt, &clist, numchans);
+        rv = exr_attr_chlist_duplicate (ctxt, &clist, channels);
         if (rv != EXR_ERR_SUCCESS) return EXR_UNLOCK_AND_RETURN (rv);
 
-        for (int c = 0; c < numchans; ++c)
-        {
-            const exr_attr_chlist_entry_t* cur = channels->entries + c;
-
-            rv = exr_attr_chlist_add_with_length (
-                ctxt,
-                &clist,
-                cur->name.str,
-                cur->name.length,
-                cur->pixel_type,
-                cur->p_linear,
-                cur->x_sampling,
-                cur->y_sampling);
-            if (rv != EXR_ERR_SUCCESS)
-            {
-                exr_attr_chlist_destroy (ctxt, &clist);
-                return EXR_UNLOCK_AND_RETURN (rv);
-            }
-        }
-
         exr_attr_chlist_destroy (ctxt, attr->chlist);
         *(attr->chlist) = clist;
     }
@@ -1931,7 +1917,14 @@
             attr->preview->height == val->height &&
             attr->preview->alloc_size > 0)
         {
-            size_t copybytes = val->width * val->height * 4;
+            size_t copybytes =
+                (size_t) val->width * (size_t) val->height * (size_t) 4;
+            if (copybytes > 0 && !val->rgba)
+                return EXR_UNLOCK_AND_RETURN (ctxt->print_error (
+                    ctxt,
+                    EXR_ERR_INVALID_ARGUMENT,
+                    "Invalid NULL preview rgba data for setting '%s'",
+                    name));
             memcpy (
                 EXR_CONST_CAST (void*, attr->preview->rgba),
                 val->rgba,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/part.c openexr-3.4.14/src/lib/OpenEXRCore/part.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/part.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/part.c	2026-08-05 04:29:55.000000000 +0300
@@ -83,20 +83,16 @@
             (uint64_t) pnamelen));
     }
 
-    rv = internal_exr_add_part (ctxt, &part, new_index);
-    if (rv != EXR_ERR_SUCCESS) return EXR_UNLOCK_AND_RETURN (rv);
-
     if (ctxt->num_parts > 0)
     {
         // ensure multi part has at least some name?
         if (!partname) partname = "";
 
-        for ( int pidx = 0; pidx < ctxt->num_parts - 1; ++pidx )
+        for ( int pidx = 0; pidx < ctxt->num_parts; ++pidx )
         {
             const exr_attribute_t* pname = ctxt->parts[pidx]->name;
             if (!pname)
             {
-                internal_exr_revert_add_part (ctxt, &part, new_index);
                 return EXR_UNLOCK_AND_RETURN (
                     ctxt->print_error (
                         ctxt,
@@ -106,7 +102,6 @@
             }
             if (!strcmp (partname, pname->string->str))
             {
-                internal_exr_revert_add_part (ctxt, &part, new_index);
                 return EXR_UNLOCK_AND_RETURN (
                     ctxt->print_error (
                         ctxt,
@@ -117,6 +112,9 @@
         }
     }
 
+    rv = internal_exr_add_part (ctxt, &part, new_index);
+    if (rv != EXR_ERR_SUCCESS) return EXR_UNLOCK_AND_RETURN (rv);
+
     part->storage_mode = type;
     switch (type)
     {
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/preview.c openexr-3.4.14/src/lib/OpenEXRCore/preview.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/preview.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/preview.c	2026-08-05 04:29:55.000000000 +0300
@@ -59,13 +59,19 @@
     uint32_t            h,
     const uint8_t*      d)
 {
+    size_t copybytes = (size_t) w * (size_t) h * (size_t) 4;
+
+    if (copybytes > 0 && !d)
+        return ctxt->print_error (
+            ctxt,
+            EXR_ERR_INVALID_ARGUMENT,
+            "Invalid NULL preview rgba data for %u x %u preview",
+            w,
+            h);
+
     exr_result_t rv = exr_attr_preview_init (ctxt, p, w, h);
-    if (rv == EXR_ERR_SUCCESS)
-    {
-        size_t copybytes = w * h * 4;
-        if (copybytes > 0)
-            memcpy (EXR_CONST_CAST (uint8_t*, p->rgba), d, copybytes);
-    }
+    if (rv == EXR_ERR_SUCCESS && copybytes > 0)
+        memcpy (EXR_CONST_CAST (uint8_t*, p->rgba), d, copybytes);
     return rv;
 }
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/string.c openexr-3.4.14/src/lib/OpenEXRCore/string.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/string.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/string.c	2026-08-05 04:29:55.000000000 +0300
@@ -15,7 +15,6 @@
 exr_result_t
 exr_attr_string_init (exr_context_t ctxt, exr_attr_string_t* s, int32_t len)
 {
-    exr_attr_string_t nil = {0};
     if (!ctxt) return EXR_ERR_MISSING_CONTEXT_ARG;
 
     if (len < 0)
@@ -31,10 +30,10 @@
             EXR_ERR_INVALID_ARGUMENT,
             "Invalid reference to string object to initialize");
 
-    *s     = nil;
     s->str = (char*) ctxt->alloc_fn ((size_t) (len + 1));
     if (s->str == NULL)
         return ctxt->standard_error (ctxt, EXR_ERR_OUT_OF_MEMORY);
+
     s->length     = len;
     s->alloc_size = len + 1;
     return EXR_ERR_SUCCESS;
@@ -46,7 +45,6 @@
 exr_attr_string_init_static_with_length (
     exr_context_t ctxt, exr_attr_string_t* s, const char* v, int32_t len)
 {
-    exr_attr_string_t nil = {0};
     if (!ctxt) return EXR_ERR_MISSING_CONTEXT_ARG;
 
     if (len < 0)
@@ -68,9 +66,9 @@
             EXR_ERR_INVALID_ARGUMENT,
             "Invalid reference to string object to initialize");
 
-    *s        = nil;
-    s->length = len;
-    s->str    = v;
+    s->length     = len;
+    s->alloc_size = 0;
+    s->str        = v;
     return EXR_ERR_SUCCESS;
 }
 
@@ -132,7 +130,11 @@
 #    pragma GCC diagnostic ignored "-Wstringop-truncation"
 #endif
             if (d)
-                strncpy (outs, d, (size_t) len);
+            {
+                size_t slen = strnlen( d, (size_t) len);
+                memcpy (outs, d, slen);
+                memset (outs + slen, '\0', ((size_t)len - slen) + 1);
+            }
             else
                 memset (outs, 0, (size_t) len);
 #ifdef _MSC_VER
@@ -143,7 +145,7 @@
 #    pragma GCC diagnostic pop
 #endif
         }
-        outs[len] = '\0';
+        else outs[0] = '\0';
     }
     return rv;
 }
@@ -210,9 +212,13 @@
 #    pragma GCC diagnostic ignored "-Wstringop-truncation"
 #endif
             if (d)
-                strncpy (sstr, d, (size_t) len);
+            {
+                size_t slen = strnlen( d, (size_t) len);
+                memcpy (sstr, d, slen);
+                memset (sstr + slen, '\0', ((size_t)len - slen) + 1);
+            }
             else
-                memset (sstr, 0, (size_t) len);
+                memset (sstr, 0, (size_t) (len + 1));
 #ifdef _MSC_VER
 #    pragma warning(pop)
 #elif defined(__clang__)
@@ -221,7 +227,7 @@
 #    pragma GCC diagnostic pop
 #endif
         }
-        sstr[len] = '\0';
+        else sstr[0] = '\0';
         return EXR_ERR_SUCCESS;
     }
     exr_attr_string_destroy (ctxt, s);
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRCore/unpack.c openexr-3.4.14/src/lib/OpenEXRCore/unpack.c
--- openexr-3.4.6+ds/src/lib/OpenEXRCore/unpack.c	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRCore/unpack.c	2026-08-05 04:29:55.000000000 +0300
@@ -232,7 +232,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 6;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 6;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -243,7 +243,7 @@
         in1 = in0 + w;
         in2 = in1 + w;
 
-        srcbuffer += w * 6; // 3 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 6; // 3 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             out[0] = one_to_native16 (in0[x]);
@@ -278,7 +278,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 6;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 6;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -289,7 +289,7 @@
         in1 = in0 + w;                     // G
         in2 = in1 + w;                     // R
 
-        srcbuffer += w * 6; // 3 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 6; // 3 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             out[0] = one_to_native16 (in2[x]);
@@ -324,7 +324,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 6;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 6;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -335,7 +335,7 @@
         in1 = in0 + w;
         in2 = in1 + w;
 
-        srcbuffer += w * 6; // 3 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 6; // 3 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             out[0] = half_to_float (one_to_native16 (in0[x]));
@@ -370,7 +370,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 6;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 6;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -381,7 +381,7 @@
         in1 = in0 + w;
         in2 = in1 + w;
 
-        srcbuffer += w * 6; // 3 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 6; // 3 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             out[0] = half_to_float (one_to_native16 (in2[x]));
@@ -420,7 +420,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 6;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 6;
 
     // planar output
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -428,7 +428,7 @@
         in0 = (const uint16_t*) srcbuffer;
         in1 = in0 + w;
         in2 = in1 + w;
-        srcbuffer += w * 6; // 3 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 6; // 3 * sizeof(uint16_t), avoid type conversion
                             /* specialise to memcpy if we can */
 #if EXR_HOST_IS_NOT_LITTLE_ENDIAN
         for (int x = 0; x < w; ++x)
@@ -476,7 +476,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 6;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 6;
 
     // planar output
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -484,7 +484,7 @@
         in0 = (const uint16_t*) srcbuffer;
         in1 = in0 + w;
         in2 = in1 + w;
-        srcbuffer += w * 6; // 3 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 6; // 3 * sizeof(uint16_t), avoid type conversion
                             /* specialise to memcpy if we can */
         half_to_float_buffer ((float*) out0, in0, w);
         half_to_float_buffer ((float*) out1, in1, w);
@@ -528,14 +528,14 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 6;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 6;
 
     for (int y = decode->user_line_begin_skip; y < h; ++y)
     {
         in0 = (const uint16_t*) srcbuffer;
         in1 = in0 + w;
         in2 = in1 + w;
-        srcbuffer += w * 6; // 3 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 6; // 3 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
             *((uint16_t*) (out0 + x * inc0)) = one_to_native16 (in0[x]);
         for (int x = 0; x < w; ++x)
@@ -584,7 +584,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 8;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 8;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -595,7 +595,7 @@
         in2              = in1 + w;
         in3              = in2 + w;
 
-        srcbuffer += w * 8; // 4 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 8; // 4 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             combined.a = one_to_native16 (in0[x]);
@@ -643,7 +643,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 8;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 8;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -654,7 +654,7 @@
         in2              = in1 + w;
         in3              = in2 + w;
 
-        srcbuffer += w * 8; // 4 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 8; // 4 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             combined.a = one_to_native16 (in0[x]);
@@ -690,7 +690,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 8;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 8;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -701,7 +701,7 @@
         in2        = in1 + w;
         in3        = in2 + w;
 
-        srcbuffer += w * 8; // 4 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 8; // 4 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             out[0] = half_to_float (one_to_native16 (in0[x]));
@@ -737,7 +737,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 8;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 8;
 
     /* interleaving case, we can do this! */
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -748,7 +748,7 @@
         in2        = in1 + w;
         in3        = in2 + w;
 
-        srcbuffer += w * 8; // 4 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 8; // 4 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
         {
             out[0] = half_to_float (one_to_native16 (in3[x]));
@@ -790,7 +790,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 8;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 8;
 
     // planar output
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -799,7 +799,7 @@
         in1 = in0 + w;
         in2 = in1 + w;
         in3 = in2 + w;
-        srcbuffer += w * 8; // 4 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 8; // 4 * sizeof(uint16_t), avoid type conversion
                             /* specialize to memcpy if we can */
 #if EXR_HOST_IS_NOT_LITTLE_ENDIAN
         for (int x = 0; x < w; ++x)
@@ -852,7 +852,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += decode->user_line_begin_skip * w * 8;
+    srcbuffer += (int64_t) decode->user_line_begin_skip * w * 8;
 
     // planar output
     for (int y = decode->user_line_begin_skip; y < h; ++y)
@@ -861,7 +861,7 @@
         in1 = in0 + w;
         in2 = in1 + w;
         in3 = in2 + w;
-        srcbuffer += w * 8; // 4 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 8; // 4 * sizeof(uint16_t), avoid type conversion
 
         half_to_float_buffer ((float*) out0, in0, w);
         half_to_float_buffer ((float*) out1, in1, w);
@@ -909,7 +909,7 @@
      * not actually using y in the loop, so just pre-increment
      * the srcbuffer for any skip
      */
-    srcbuffer += w * decode->user_line_begin_skip * 8;
+    srcbuffer += (int64_t) w * decode->user_line_begin_skip * 8;
 
     for (int y = decode->user_line_begin_skip; y < h; ++y)
     {
@@ -917,7 +917,7 @@
         in1 = in0 + w;
         in2 = in1 + w;
         in3 = in2 + w;
-        srcbuffer += w * 8; // 4 * sizeof(uint16_t), avoid type conversion
+        srcbuffer += (int64_t) w * 8; // 4 * sizeof(uint16_t), avoid type conversion
         for (int x = 0; x < w; ++x)
             *((uint16_t*) (out0 + x * inc0)) = one_to_native16 (in0[x]);
         for (int x = 0; x < w; ++x)
@@ -952,7 +952,7 @@
     for (int c = 0; c < decode->channel_count; ++c)
     {
         exr_coding_channel_info_t* decc = (decode->channels + c);
-        srcbuffer += decc->width * decode->user_line_begin_skip * 2;
+        srcbuffer += (int64_t) decc->width * decode->user_line_begin_skip * 2;
     }
     h -= decode->user_line_begin_skip;
 
@@ -1001,7 +1001,7 @@
                 }
             }
 #endif
-            srcbuffer += w * 2;
+            srcbuffer += (int64_t) w * 2;
         }
     }
     return EXR_ERR_SUCCESS;
@@ -1027,7 +1027,7 @@
     for (int c = 0; c < decode->channel_count; ++c)
     {
         exr_coding_channel_info_t* decc = (decode->channels + c);
-        srcbuffer += decc->width * decode->user_line_begin_skip * 4;
+        srcbuffer += (int64_t) decc->width * decode->user_line_begin_skip * 4;
     }
     h -= decode->user_line_begin_skip;
 
@@ -1076,7 +1076,7 @@
                 }
             }
 #endif
-            srcbuffer += w * 4;
+            srcbuffer += (int64_t) w * 4;
         }
     }
     return EXR_ERR_SUCCESS;
@@ -1263,19 +1263,21 @@
                 if ((cury % decc->y_samples) != 0) continue;
                 if (y < uls || !cdata)
                 {
-                    srcbuffer += w * bpc;
+                    srcbuffer += (int64_t) w * bpc;
                     continue;
                 }
 
+                const int fb_y = uls + (int) decode->chunk.start_y;
                 cdata +=
-                    ((uint64_t) ((y - uls) / decc->y_samples) *
+                    ((uint64_t) (cury / decc->y_samples -
+                                  fb_y / decc->y_samples) *
                      (uint64_t) decc->user_line_stride);
             }
             else
             {
                 if (y < uls || !cdata)
                 {
-                    srcbuffer += w * bpc;
+                    srcbuffer += (int64_t) w * bpc;
                     continue;
                 }
 
@@ -1283,7 +1285,7 @@
             }
 
             UNPACK_SAMPLES (w)
-            srcbuffer += w * bpc;
+            srcbuffer += (int64_t) w * bpc;
         }
     }
     return EXR_ERR_SUCCESS;
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfCheckFile.h openexr-3.4.14/src/lib/OpenEXRUtil/ImfCheckFile.h
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfCheckFile.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfCheckFile.h	2026-08-05 04:29:55.000000000 +0300
@@ -27,6 +27,8 @@
 //
 // if runCoreCheck is true, only uses the OpenEXRCore (C) API, otherwise uses the OpenEXR (C++) API
 //
+// fileName is a UTF-8 encoded path; see ImfIO.h.
+//
 
 IMFUTIL_EXPORT bool checkOpenEXRFile (
     const char* fileName,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfDeepImageChannel.h openexr-3.4.14/src/lib/OpenEXRUtil/ImfDeepImageChannel.h
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfDeepImageChannel.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfDeepImageChannel.h	2026-08-05 04:29:55.000000000 +0300
@@ -248,14 +248,14 @@
 inline T* const*
 TypedDeepImageChannel<T>::row (int r)
 {
-    return _base + r * pixelsPerRow ();
+    return _sampleListPointers + r * pixelsPerRow ();
 }
 
 template <class T>
 inline const T* const*
 TypedDeepImageChannel<T>::row (int r) const
 {
-    return _base + r * pixelsPerRow ();
+    return _sampleListPointers + r * pixelsPerRow ();
 }
 
 #ifndef COMPILING_IMF_DEEP_IMAGE_CHANNEL
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfDeepImageIO.h openexr-3.4.14/src/lib/OpenEXRUtil/ImfDeepImageIO.h
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfDeepImageIO.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfDeepImageIO.h	2026-08-05 04:29:55.000000000 +0300
@@ -11,6 +11,8 @@
 //      Functions to load deep images from OpenEXR files
 //      and to save deep images in OpenEXR files.
 //
+//      File name parameters are UTF-8 paths; see ImfIO.h.
+//
 //----------------------------------------------------------------------------
 
 #include "ImfNamespace.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfFlatImageChannel.h openexr-3.4.14/src/lib/OpenEXRUtil/ImfFlatImageChannel.h
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfFlatImageChannel.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfFlatImageChannel.h	2026-08-05 04:29:55.000000000 +0300
@@ -199,14 +199,14 @@
 inline T*
 TypedFlatImageChannel<T>::row (int r)
 {
-    return _base + r * pixelsPerRow ();
+    return _pixels + r * pixelsPerRow ();
 }
 
 template <class T>
 inline const T*
 TypedFlatImageChannel<T>::row (int n) const
 {
-    return _base + n * pixelsPerRow ();
+    return _pixels + n * pixelsPerRow ();
 }
 
 #ifndef COMPILING_IMF_FLAT_IMAGE_CHANNEL
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfFlatImageIO.h openexr-3.4.14/src/lib/OpenEXRUtil/ImfFlatImageIO.h
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfFlatImageIO.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfFlatImageIO.h	2026-08-05 04:29:55.000000000 +0300
@@ -11,6 +11,8 @@
 //      Functions to load flat images from OpenEXR files
 //      and to save flat images in OpenEXR files.
 //
+//      File name parameters are UTF-8 paths; see ImfIO.h.
+//
 //----------------------------------------------------------------------------
 
 #include "ImfFlatImage.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImageChannel.cpp openexr-3.4.14/src/lib/OpenEXRUtil/ImfImageChannel.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImageChannel.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfImageChannel.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -13,6 +13,9 @@
 #include "ImfImageLevel.h"
 #include <Iex.h>
 
+#include <cstdint>
+#include <limits>
+
 using namespace IMATH_NAMESPACE;
 using namespace IEX_NAMESPACE;
 using namespace std;
@@ -46,6 +49,13 @@
 void
 ImageChannel::resize ()
 {
+    if (_xSampling < 1 || _ySampling < 1)
+    {
+        throw ArgExc (
+            "The x and y sampling rates for an image channel must be at "
+            "least 1.");
+    }
+
     const Box2i& dataWindow = level ().dataWindow ();
 
     if (dataWindow.min.x % _xSampling || dataWindow.min.y % _ySampling)
@@ -67,7 +77,17 @@
 
     _pixelsPerRow    = width / _xSampling;
     _pixelsPerColumn = height / _ySampling;
-    _numPixels       = _pixelsPerRow * _pixelsPerColumn;
+
+    const uint64_t w64 = static_cast<uint64_t> (_pixelsPerRow);
+    const uint64_t h64 = static_cast<uint64_t> (_pixelsPerColumn);
+    const uint64_t maxPixels =
+        static_cast<uint64_t> (std::numeric_limits<size_t>::max ());
+    if (w64 != 0 && h64 > maxPixels / w64)
+    {
+        throw ArgExc ("Image channel dimensions too large.");
+    }
+
+    _numPixels = static_cast<size_t> (w64 * h64);
 }
 
 void
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImage.cpp openexr-3.4.14/src/lib/OpenEXRUtil/ImfImage.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImage.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfImage.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -14,6 +14,8 @@
 #include <ImfChannelList.h>
 #include <algorithm>
 #include <cassert>
+#include <climits>
+#include <cstdint>
 
 using namespace IMATH_NAMESPACE;
 using namespace IEX_NAMESPACE;
@@ -24,37 +26,71 @@
 namespace
 {
 
+int64_t
+pixelExtent (int64_t minCoord, int64_t maxCoord)
+{
+    if (maxCoord < minCoord) return 0;
+
+    return maxCoord - minCoord + 1;
+}
+
+void
+validateDataWindow (const Box2i64& dw)
+{
+    if (dw.min.x > dw.max.x + 1 || dw.min.y > dw.max.y + 1)
+    {
+        THROW (ArgExc, "Invalid data window for image resize.");
+    }
+
+    if (dw.min.x <= -(INT_MAX / 2) || dw.min.y <= -(INT_MAX / 2) ||
+        dw.max.x >= (INT_MAX / 2) || dw.max.y >= (INT_MAX / 2))
+    {
+        THROW (ArgExc, "Invalid data window for image resize.");
+    }
+
+    const int64_t w = pixelExtent (dw.min.x, dw.max.x);
+    const int64_t h = pixelExtent (dw.min.y, dw.max.y);
+
+    if (w < 0 || h < 0 || w > INT_MAX || h > INT_MAX)
+    {
+        THROW (ArgExc, "Invalid data window for image resize.");
+    }
+}
+
 int
-levelSize (int min, int max, int l, LevelRoundingMode levelRoundingMode)
+levelSize (
+    int64_t           minCoord,
+    int64_t           maxCoord,
+    int               l,
+    LevelRoundingMode levelRoundingMode)
 {
     assert (l >= 0);
 
-    if (max < min) return 0;
+    int64_t a = pixelExtent (minCoord, maxCoord);
+    if (a == 0) return 0;
+
+    if (a > INT_MAX)
+    {
+        THROW (ArgExc, "Invalid level size for image resize.");
+    }
 
-    int a    = max - min + 1;
-    int b    = (1 << l);
-    int size = a / b;
+    int64_t b    = int64_t (1) << l;
+    int64_t size = a / b;
 
     if (levelRoundingMode == ROUND_UP && size * b < a) size += 1;
 
-    return std::max (size, 1);
-}
+    size = std::max (size, int64_t (1));
 
-Box2i
-computeDataWindowForLevel (
-    const Box2i& dataWindow, int lx, int ly, LevelRoundingMode lrMode)
-{
-    V2i levelMax =
-        dataWindow.min +
-        V2i (
-            levelSize (dataWindow.min.x, dataWindow.max.x, lx, lrMode) - 1,
-            levelSize (dataWindow.min.y, dataWindow.max.y, ly, lrMode) - 1);
+    if (size > INT_MAX)
+    {
+        THROW (ArgExc, "Invalid level size for image resize.");
+    }
 
-    return Box2i (dataWindow.min, levelMax);
+    return int (size);
 }
 
 int
-floorLog2 (int x)
+floorLog2 (int64_t x)
 {
     //
     // For x > 0, floorLog2(y) returns floor(log(x)/log(2)).
@@ -72,7 +108,7 @@
 }
 
 int
-ceilLog2 (int x)
+ceilLog2 (int64_t x)
 {
     //
     // For x > 0, ceilLog2(y) returns ceil(log(x)/log(2)).
@@ -93,7 +129,7 @@
 }
 
 int
-roundLog2 (int x, LevelRoundingMode levelRoundingMode)
+roundLog2 (int64_t x, LevelRoundingMode levelRoundingMode)
 {
     if (x < 1) return 1;
 
@@ -102,10 +138,13 @@
 
 int
 computeNumXLevels (
-    const Box2i&      dataWindow,
+    const Box2i64&    dw,
     LevelMode         levelMode,
     LevelRoundingMode levelRoundingMode)
 {
+    const int64_t w = pixelExtent (dw.min.x, dw.max.x);
+    const int64_t h = pixelExtent (dw.min.y, dw.max.y);
+
     int n = 0;
 
     switch (levelMode)
@@ -113,21 +152,12 @@
         case ONE_LEVEL: n = 1; break;
 
         case MIPMAP_LEVELS:
-
-        {
-            int w = dataWindow.max.x - dataWindow.min.x + 1;
-            int h = dataWindow.max.y - dataWindow.min.y + 1;
-            n     = roundLog2 (std::max (w, h), levelRoundingMode) + 1;
-        }
-        break;
+            n = roundLog2 (std::max (w, h), levelRoundingMode) + 1;
+            break;
 
         case RIPMAP_LEVELS:
-
-        {
-            int w = dataWindow.max.x - dataWindow.min.x + 1;
-            n     = roundLog2 (w, levelRoundingMode) + 1;
-        }
-        break;
+            n = roundLog2 (w, levelRoundingMode) + 1;
+            break;
 
         default: assert (false);
     }
@@ -137,10 +167,13 @@
 
 int
 computeNumYLevels (
-    const Box2i&      dataWindow,
+    const Box2i64&    dw,
     LevelMode         levelMode,
     LevelRoundingMode levelRoundingMode)
 {
+    const int64_t w = pixelExtent (dw.min.x, dw.max.x);
+    const int64_t h = pixelExtent (dw.min.y, dw.max.y);
+
     int n = 0;
 
     switch (levelMode)
@@ -148,21 +181,12 @@
         case ONE_LEVEL: n = 1; break;
 
         case MIPMAP_LEVELS:
-
-        {
-            int w = dataWindow.max.x - dataWindow.min.x + 1;
-            int h = dataWindow.max.y - dataWindow.min.y + 1;
-            n     = roundLog2 (std::max (w, h), levelRoundingMode) + 1;
-        }
-        break;
+            n = roundLog2 (std::max (w, h), levelRoundingMode) + 1;
+            break;
 
         case RIPMAP_LEVELS:
-
-        {
-            int h = dataWindow.max.y - dataWindow.min.y + 1;
-            n     = roundLog2 (h, levelRoundingMode) + 1;
-        }
-        break;
+            n = roundLog2 (h, levelRoundingMode) + 1;
+            break;
 
         default: assert (false);
     }
@@ -261,8 +285,7 @@
                 << lx << ".");
     }
 
-    return levelSize (
-        _dataWindow.min.x, _dataWindow.max.x, lx, _levelRoundingMode);
+    return levelSize (_dataWindow.min.x, _dataWindow.max.x, lx, _levelRoundingMode);
 }
 
 int
@@ -277,8 +300,7 @@
                 << ly << ".");
     }
 
-    return levelSize (
-        _dataWindow.min.y, _dataWindow.max.y, ly, _levelRoundingMode);
+    return levelSize (_dataWindow.min.y, _dataWindow.max.y, ly, _levelRoundingMode);
 }
 
 void
@@ -297,12 +319,20 @@
     {
         clearLevels ();
 
-        int nx = computeNumXLevels (dataWindow, levelMode, levelRoundingMode);
-        int ny = computeNumYLevels (dataWindow, levelMode, levelRoundingMode);
+        const Box2i64 dw (dataWindow.min, dataWindow.max);
+
+        validateDataWindow (dw);
+
+        int nx = computeNumXLevels (dw, levelMode, levelRoundingMode);
+        int ny = computeNumYLevels (dw, levelMode, levelRoundingMode);
 
         _levels.resizeErase (ny, nx);
 
         for (int y = 0; y < ny; ++y)
+            for (int x = 0; x < nx; ++x)
+                _levels[y][x] = nullptr;
+
+        for (int y = 0; y < ny; ++y)
         {
             for (int x = 0; x < nx; ++x)
             {
@@ -312,8 +342,9 @@
                     continue;
                 }
 
-                Box2i levelDataWindow = computeDataWindowForLevel (
-                    dataWindow, x, y, levelRoundingMode);
+                const V2i ls (levelSize (dw.min.x, dw.max.x, x, levelRoundingMode) - 1,
+                              levelSize (dw.min.y, dw.max.y, y, levelRoundingMode) - 1);
+                const Box2i levelDataWindow (dataWindow.min, dataWindow.min + ls);
 
                 _levels[y][x] = newLevel (x, y, levelDataWindow);
 
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImageIO.h openexr-3.4.14/src/lib/OpenEXRUtil/ImfImageIO.h
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImageIO.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfImageIO.h	2026-08-05 04:29:55.000000000 +0300
@@ -11,6 +11,8 @@
 //      Functions to load flat or deep images from OpenEXR files
 //      and to save flat or deep images in OpenEXR files.
 //
+//      File name parameters are UTF-8 paths; see ImfIO.h.
+//
 //----------------------------------------------------------------------------
 
 #include "ImfUtilExport.h"
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImageLevel.cpp openexr-3.4.14/src/lib/OpenEXRUtil/ImfImageLevel.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfImageLevel.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfImageLevel.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -12,6 +12,7 @@
 #include "ImfImageLevel.h"
 #include <Iex.h>
 #include <cassert>
+#include <cstdint>
 
 using namespace IMATH_NAMESPACE;
 using namespace IEX_NAMESPACE;
@@ -36,8 +37,8 @@
 void
 ImageLevel::resize (const Box2i& dataWindow)
 {
-    if (dataWindow.max.x < dataWindow.min.x - 1 ||
-        dataWindow.max.y < dataWindow.min.y - 1)
+    if (int64_t (dataWindow.min.x) > int64_t (dataWindow.max.x) + 1 ||
+        int64_t (dataWindow.min.y) > int64_t (dataWindow.max.y) + 1)
     {
         THROW (
             ArgExc,
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfSampleCountChannel.cpp openexr-3.4.14/src/lib/OpenEXRUtil/ImfSampleCountChannel.cpp
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfSampleCountChannel.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfSampleCountChannel.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -32,6 +32,15 @@
 
     if (n == 0) return 0;
 
+    if (n > 0x80000000u)
+    {
+        THROW (
+            ArgExc,
+            "Sample count "
+                << n
+                << " is too large (cannot round up to next power of two)");
+    }
+
     unsigned int s = 1;
 
     while (s < n)
@@ -112,6 +121,8 @@
     // arrays that describe it.
     //
 
+    boundsCheck (x, y);
+
     size_t i = (_base + y * pixelsPerRow () + x) - _numSamples;
 
     if (newNumSamples <= _numSamples[i])
@@ -222,8 +233,18 @@
 void
 SampleCountChannel::set (int r, unsigned int newNumSamples[])
 {
+    if (r < 0 || r >= pixelsPerColumn ())
+    {
+        THROW (
+            ArgExc,
+            "Attempt to set sample counts for row "
+            << r
+            << " in an image channel with "
+            << pixelsPerColumn () << " rows.");
+    }
+
     int x = level ().dataWindow ().min.x;
-    int y = r + level ().dataWindow ().min.x;
+    int y = r + level ().dataWindow ().min.y;
 
     for (int i = 0; i < pixelsPerRow (); ++i, ++x)
         set (x, y, newNumSamples[i]);
diff -Nru openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfSampleCountChannel.h openexr-3.4.14/src/lib/OpenEXRUtil/ImfSampleCountChannel.h
--- openexr-3.4.6+ds/src/lib/OpenEXRUtil/ImfSampleCountChannel.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/lib/OpenEXRUtil/ImfSampleCountChannel.h	2026-08-05 04:29:55.000000000 +0300
@@ -316,7 +316,7 @@
 inline const unsigned int*
 SampleCountChannel::row (int n) const
 {
-    return _base + n * pixelsPerRow ();
+    return _numSamples + n * pixelsPerRow ();
 }
 
 OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT
diff -Nru openexr-3.4.6+ds/src/test/bin/test_exrmetrics.py openexr-3.4.14/src/test/bin/test_exrmetrics.py
--- openexr-3.4.6+ds/src/test/bin/test_exrmetrics.py	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/bin/test_exrmetrics.py	2026-08-05 04:29:55.000000000 +0300
@@ -72,4 +72,9 @@
 data = json.loads(result.stdout)
 assert(len(data)==1),"\n Unexpected list size in JSON object"
 
+# test --part-disk-size json output
+result = do_run ([exrmetrics, test_images["multipart"], "--part-disk-size"])
+data = json.loads(result.stdout)
+assert(len(data)==1),"\n Unexpected list size in JSON object"
+
 print("success")
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/base_units.cpp openexr-3.4.14/src/test/OpenEXRCoreTest/base_units.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/base_units.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/base_units.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -396,7 +396,7 @@
         EXRCORE_TEST (false);
     }
 
-#if defined(__x86_64__) || defined(_M_X64)
+#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
     if (has_native_half () != (hf16c && havx))
     {
         std::cerr << "CPU Id test has native half mismatch" << std::endl;
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/CMakeLists.txt openexr-3.4.14/src/test/OpenEXRCoreTest/CMakeLists.txt
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/CMakeLists.txt	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/CMakeLists.txt	2026-08-05 04:29:55.000000000 +0300
@@ -96,6 +96,7 @@
  testReadMultiPart
  testReadDeep
  testReadUnpack
+ testSamplingCalcs
 
  testWriteBadArgs
  testWriteBadFiles
@@ -127,6 +128,7 @@
  testDWAACompression
  testDWABCompression
  testHTChannelMap
+ testHTHeaderBounds
  testDeepNoCompression
  testDeepZIPCompression
  testDeepZIPSCompression
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/compression.cpp openexr-3.4.14/src/test/OpenEXRCoreTest/compression.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/compression.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/compression.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -518,22 +518,24 @@
         const char* tagb,
         const char* chan)
     {
-        union
-        {
-            uint32_t iv;
-            float    fv;
-        } a, b;
-        a.fv = af;
-        b.fv = bf;
-        if (a.iv != b.iv)
+        // On some architectures (Windows 32‑bit x86, i686 with x87 extended
+        // precision, aarch64, ppc64le, arm7), the C and C++ codepaths may
+        // generate NaN values that are not bitwise identical. Accept these
+        // special cases as equal.
+        const bool bothNaN = std::isnan(af) && std::isnan(bf);
+        if (bothNaN)
+            return;
+
+        uint32_t a,b; memcpy(&a,&af,sizeof a); memcpy(&b,&bf,sizeof b);
+        if (a != b)
         {
             std::cout << chan << " float at " << x << ", " << y
-                      << " not equal: " << taga << " 0x" << std::hex << a.iv
+                      << " not equal: " << taga << " 0x" << std::hex << a
                       << std::dec << " (" << af << ") vs " << tagb << " "
-                      << std::hex << b.iv << std::dec << " (" << bf << ")"
+                      << std::hex << b << std::dec << " (" << bf << ")"
                       << std::endl;
         }
-        EXRCORE_TEST (a.iv == b.iv);
+        EXRCORE_TEST (a == b);
     }
     void
     compareExact (const pixels& o, const char* otag, const char* selftag) const
@@ -1750,6 +1752,58 @@
     EXRCORE_TEST (! make_channel_map (3, channels_2, cs_to_file_ch));
 }
 
+static bool
+read_header_throws (void* buffer, size_t max_sz)
+{
+    std::vector<CodestreamChannelInfo> map;
+    try
+    {
+        read_header (buffer, max_sz, map);
+        return false;
+    }
+    catch (...)
+    {
+        return true;
+    }
+}
+
+void
+testHTHeaderBounds (const std::string& tempdir)
+{
+    (void) tempdir;
+
+    exr_coding_channel_info_t channels[] = {
+        { "R" }, { "G" }, { "B" }
+    };
+    std::vector<CodestreamChannelInfo> cs_to_file_ch;
+    EXRCORE_TEST (make_channel_map (3, channels, cs_to_file_ch));
+
+    uint8_t buf[64];
+    const size_t hdr_sz =
+        write_header (buf, sizeof (buf), cs_to_file_ch);
+    EXRCORE_TEST (hdr_sz > HEADER_SZ);
+
+    std::vector<CodestreamChannelInfo> read_map;
+    EXRCORE_TEST (read_header (buf, hdr_sz, read_map) == hdr_sz);
+    EXRCORE_TEST (read_map.size () == 3);
+
+    EXRCORE_TEST (read_header_throws (buf, hdr_sz - 1));
+
+    uint8_t bad_plen[64];
+    memcpy (bad_plen, buf, hdr_sz);
+    bad_plen[2] = 0xff;
+    bad_plen[3] = 0xff;
+    bad_plen[4] = 0xff;
+    bad_plen[5] = 0xf0;
+    EXRCORE_TEST (read_header_throws (bad_plen, sizeof (bad_plen)));
+
+    uint8_t bad_nch[64];
+    memcpy (bad_nch, buf, hdr_sz);
+    bad_nch[HEADER_SZ]     = 0;
+    bad_nch[HEADER_SZ + 1] = 32;
+    EXRCORE_TEST (read_header_throws (bad_nch, hdr_sz));
+}
+
 void
 testDeepNoCompression (const std::string& tempdir)
 {}
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/compression.h openexr-3.4.14/src/test/OpenEXRCoreTest/compression.h
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/compression.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/compression.h	2026-08-05 04:29:55.000000000 +0300
@@ -23,6 +23,7 @@
 void testDWAACompression (const std::string& tempdir);
 void testDWABCompression (const std::string& tempdir);
 void testHTChannelMap (const std::string& tempdir);
+void testHTHeaderBounds (const std::string& tempdir);
 
 void testDeepNoCompression (const std::string& tempdir);
 void testDeepZIPCompression (const std::string& tempdir);
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/general_attr.cpp openexr-3.4.14/src/test/OpenEXRCoreTest/general_attr.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/general_attr.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/general_attr.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -250,7 +250,13 @@
     EXRCORE_TEST_RVAL (exr_attr_string_destroy (f, &s));
 
     size_t nbytes = (size_t) INT32_MAX + 1;
-    char*  tmp    = (char*) malloc (nbytes + 1);
+#if defined(__SIZEOF_SIZE_T__) && __SIZEOF_SIZE_T__ >= 8
+    char* tmp = (char*) malloc (nbytes + 1);
+#else
+    // 32-bit: nbytes+1 exceeds max object size; skip to avoid -Walloc-size-larger-than=
+    (void) nbytes; // reference nbytes to avoid unused variable warning
+    char* tmp = nullptr;
+#endif
     // might be on a low memory machine, in which case just skip the test
     if (tmp)
     {
@@ -768,6 +774,20 @@
         1, EXR_ERR_OUT_OF_MEMORY, exr_attr_chlist_duplicate (f, &cl2, &cl));
     EXRCORE_TEST_RVAL (exr_attr_chlist_destroy (f, &cl2));
 
+    {
+        exr_attr_chlist_t bad = {0};
+        bad.num_channels      = 1;
+        bad.entries           = NULL;
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT,
+            exr_attr_chlist_duplicate (f, &cl2, &bad));
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT, exr_set_channels (f, 0, &bad));
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT,
+            exr_attr_set_channels (f, 0, "badChlist", &bad));
+    }
+
     /* without a file, max will be 31 */
     EXRCORE_TEST_RVAL_FAIL (
         EXR_ERR_NAME_TOO_LONG,
@@ -842,6 +862,35 @@
     // make sure we can re-delete something?
     EXRCORE_TEST_RVAL (exr_attr_preview_destroy (f, &p));
 
+    EXRCORE_TEST_RVAL_FAIL (
+        EXR_ERR_INVALID_ARGUMENT, exr_attr_preview_create (f, &p, 1, 1, NULL));
+
+    {
+        exr_attr_preview_t preview;
+        preview.width      = 1;
+        preview.height     = 1;
+        preview.alloc_size = 0;
+        preview.rgba       = NULL;
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT,
+            exr_attr_set_preview (f, 0, "badPreview", &preview));
+    }
+
+    {
+        exr_attr_preview_t preview;
+        preview.width      = 1;
+        preview.height     = 1;
+        preview.alloc_size = 4;
+        preview.rgba       = data1x1;
+        EXRCORE_TEST_RVAL (
+            exr_attr_set_preview (f, 0, "goodPreview", &preview));
+
+        preview.rgba = NULL;
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT,
+            exr_attr_set_preview (f, 0, "goodPreview", &preview));
+    }
+
     EXRCORE_TEST_RVAL_FAIL_MALLOC (
         EXR_ERR_OUT_OF_MEMORY, exr_attr_preview_create (f, &p, 1, 1, data1x1));
 }
@@ -1016,13 +1065,45 @@
 
     EXRCORE_TEST_RVAL (exr_attr_bytes_init (f, &b, 0, 0));
     EXRCORE_TEST_RVAL (exr_attr_bytes_destroy (f, &b));
-    EXRCORE_TEST_RVAL (exr_attr_bytes_init (f, &b, 0, 0));
     EXRCORE_TEST_RVAL (exr_attr_bytes_create (f, &b, 0, 4, NULL, data4));
     EXRCORE_TEST_RVAL (exr_attr_bytes_copy (f, &b2, &b));
     EXRCORE_TEST_RVAL (exr_attr_bytes_destroy (f, &b2));
     EXRCORE_TEST_RVAL (exr_attr_bytes_destroy (f, &b));
 
     EXRCORE_TEST_RVAL_FAIL (
+        EXR_ERR_INVALID_ARGUMENT,
+        exr_attr_bytes_create (f, &b, 1, 1, NULL, data4));
+    EXRCORE_TEST_RVAL_FAIL (
+        EXR_ERR_INVALID_ARGUMENT,
+        exr_attr_bytes_create (f, &b, 0, 4, "hint", NULL));
+
+    {
+        exr_attr_bytes_t bytes;
+        bytes.size        = 1;
+        bytes.data        = data4;
+        bytes.hint_length = 1;
+        bytes.type_hint   = NULL;
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT,
+            exr_attr_set_bytes (f, 0, "badBytes", &bytes));
+    }
+
+    {
+        exr_attr_bytes_t bytes;
+        bytes.size        = 4;
+        bytes.data        = data4;
+        bytes.hint_length = 11;
+        bytes.type_hint   = "a cool hint";
+        EXRCORE_TEST_RVAL (
+            exr_attr_set_bytes (f, 0, "goodBytes", &bytes));
+
+        bytes.type_hint = NULL;
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT,
+            exr_attr_set_bytes (f, 0, "goodBytes", &bytes));
+    }
+
+    EXRCORE_TEST_RVAL_FAIL (
         EXR_ERR_INVALID_ARGUMENT, exr_attr_bytes_copy (f, &b2, NULL));
 }
 
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/main.cpp openexr-3.4.14/src/test/OpenEXRCoreTest/main.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/main.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/main.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -178,6 +178,7 @@
     TEST (testReadMultiPart, "core_read");
     TEST (testReadDeep, "core_read");
     TEST (testReadUnpack, "core_read");
+    TEST (testSamplingCalcs, "core_read");
 
     TEST (testWriteBadArgs, "core_write");
     TEST (testWriteBadFiles, "core_write");
@@ -209,6 +210,7 @@
     TEST (testDWAACompression, "core_compression");
     TEST (testDWABCompression, "core_compression");
     TEST (testHTChannelMap, "core_compression");
+    TEST (testHTHeaderBounds, "core_compression");
 
     TEST (testDeepNoCompression, "core_compression");
     TEST (testDeepZIPCompression, "core_compression");
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/read.cpp openexr-3.4.14/src/test/OpenEXRCoreTest/read.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/read.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/read.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -616,3 +616,121 @@
 
     exr_finish (&f);
 }
+
+#include "../../lib/OpenEXRCore/internal_util.h"
+
+static inline int
+compute_sampled_height_p (int height, int y_sampling, int start_y)
+{
+    int nlines;
+
+    if (y_sampling <= 1) return height;
+
+    if (height == 1)
+        nlines = (start_y % y_sampling) == 0 ? 1 : 0;
+    else
+    {
+        int off, tmph;
+
+        /* computed the number of times y % ysampling == 0, by
+         * computing interval based on first and last time that occurs
+         * on the given range
+         */
+        if (start_y < 0)
+        {
+            off = -start_y % y_sampling;
+        }
+        else
+        {
+            off = start_y % y_sampling;
+            if (off != 0)
+                off = (y_sampling - off);
+        }
+
+        tmph = height - off;
+        if (tmph == 0) return 0;
+        --tmph;
+        nlines = tmph / y_sampling + 1;
+    }
+
+    return nlines;
+}
+
+static inline int hardway_height (int height, int y_sampling, int start_y)
+{
+    int nlines = 0;
+    int end = start_y + height;
+
+    if (y_sampling <= 1) return height;
+
+    if (height == 1)
+        return (start_y % y_sampling) == 0 ? 1 : 0;
+
+    for ( int y = start_y; y < end; ++y )
+    {
+        if (y % y_sampling != 0) continue;
+        ++nlines;
+    }
+    return nlines;
+}
+
+static inline int hardway_height_p (int height, int y_sampling, int start_y)
+{
+    int nlines = 0;
+    int end = start_y + height;
+    int off = 0;
+
+    if (y_sampling <= 1) return height;
+
+    if (height == 1)
+        return (start_y % y_sampling) == 0 ? 1 : 0;
+
+    for ( int y = start_y; y < end; ++y )
+    {
+        if (y % y_sampling != 0)
+        {
+            if (nlines == 0) ++off;
+            continue;
+        }
+        ++nlines;
+    }
+    return nlines;
+}
+
+static void test_lpc( int samp, int h, int lpc )
+{
+    for ( int y = -100; y < 100; ++y )
+    {
+        int sy = y * samp;
+        int ey = sy + h - 1;
+        //printf( "============= %d - %d ============\n", sy, ey);
+        for ( int ty = sy; ty < ey; ty += lpc )
+        {
+            int th = ty + lpc;
+            if (th > ey) th = ey - ty + 1;
+            else th = lpc;
+
+            int nl = compute_sampled_height(th, samp, ty);
+            int hnl = hardway_height(th, samp, ty);
+            if (nl != hnl)
+            {
+                compute_sampled_height_p(th, samp, ty);
+                hardway_height_p(th, samp, ty);
+                printf( "ty %d h %d (%d) samp %d lpc %d => nl %d hnl %d\n",
+                        ty, th, h, samp, lpc, nl, hnl );
+                EXRCORE_TEST(nl == hnl);
+            }
+        }
+    }
+}
+
+void testSamplingCalcs (const std::string& tempdir)
+{
+    int lines[] = { 1, 16, 32, 256 };
+
+    for ( int s = 2; s < 11; ++s )
+    {
+        for ( int lpc = 0; lpc < 4; ++lpc )
+            test_lpc( s, 1000, lines[lpc] );
+    }
+}
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/read.h openexr-3.4.14/src/test/OpenEXRCoreTest/read.h
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/read.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/read.h	2026-08-05 04:29:55.000000000 +0300
@@ -22,4 +22,6 @@
 
 void testReadUnpack (const std::string& tempdir);
 
+void testSamplingCalcs (const std::string& tempdir);
+
 #endif // OPENEXR_CORE_TEST_READ_H
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRCoreTest/write.cpp openexr-3.4.14/src/test/OpenEXRCoreTest/write.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRCoreTest/write.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRCoreTest/write.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -88,8 +88,31 @@
     EXRCORE_TEST_RVAL (
         exr_add_part (outf, "beauty", EXR_STORAGE_SCANLINE, &partidx));
     EXRCORE_TEST (partidx == 0);
-    EXRCORE_TEST_RVAL (exr_get_count (outf, &partidx));
+    {
+        uint64_t chunk_table_offset = 0;
+        int      count              = 0;
+        EXRCORE_TEST_RVAL (
+            exr_get_chunk_table_offset (outf, 0, &chunk_table_offset));
+        EXRCORE_TEST_RVAL_FAIL (
+            EXR_ERR_INVALID_ARGUMENT,
+            exr_get_chunk_table_offset (outf, 0, NULL));
+        EXRCORE_TEST_RVAL (exr_get_count (outf, &count));
+        EXRCORE_TEST (count == 1);
+    }
+    /* dup name check */
+    EXRCORE_TEST_RVAL_FAIL (
+        EXR_ERR_INVALID_ARGUMENT,
+        exr_add_part (outf, "beauty", EXR_STORAGE_SCANLINE, &partidx));
+    /* this add should work, making 2 parts */
+    EXRCORE_TEST_RVAL (
+        exr_add_part (outf, "other", EXR_STORAGE_SCANLINE, &partidx));
     EXRCORE_TEST (partidx == 1);
+    /* test repeated add failure doesn't leak */
+    EXRCORE_TEST_RVAL_FAIL (
+        EXR_ERR_INVALID_ARGUMENT,
+        exr_add_part (outf, "beauty", EXR_STORAGE_SCANLINE, &partidx));
+    EXRCORE_TEST_RVAL (exr_get_count (outf, &partidx));
+    EXRCORE_TEST (partidx == 2);
     partidx = 0;
 
     exr_chunk_info_t cinfo;
@@ -105,7 +128,7 @@
         exr_get_name (outf, partidx - 1, &partname));
     EXRCORE_TEST_RVAL_FAIL (
         EXR_ERR_ARGUMENT_OUT_OF_RANGE,
-        exr_get_name (outf, partidx + 1, &partname));
+        exr_get_name (outf, partidx + 2, &partname));
     EXRCORE_TEST_RVAL_FAIL (
         EXR_ERR_INVALID_ARGUMENT, exr_get_name (outf, partidx, NULL));
     EXRCORE_TEST_RVAL (exr_get_name (outf, partidx, &partname));
@@ -119,7 +142,7 @@
         exr_get_storage (outf, partidx - 1, &storage));
     EXRCORE_TEST_RVAL_FAIL (
         EXR_ERR_ARGUMENT_OUT_OF_RANGE,
-        exr_get_storage (outf, partidx + 1, &storage));
+        exr_get_storage (outf, partidx + 2, &storage));
     EXRCORE_TEST_RVAL_FAIL (
         EXR_ERR_INVALID_ARGUMENT, exr_get_storage (outf, partidx, NULL));
     EXRCORE_TEST_RVAL (exr_get_storage (outf, partidx, &storage));
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/CMakeLists.txt openexr-3.4.14/src/test/OpenEXRTest/CMakeLists.txt
--- openexr-3.4.6+ds/src/test/OpenEXRTest/CMakeLists.txt	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/CMakeLists.txt	2026-08-05 04:29:55.000000000 +0300
@@ -89,8 +89,6 @@
   testMultiView.h
   testNativeFormat.cpp
   testNativeFormat.h
-  testOptimized.cpp
-  testOptimized.h
   testOptimizedInterleavePatterns.cpp
   testOptimizedInterleavePatterns.h
   testPartHelper.cpp
@@ -190,7 +188,6 @@
  testMultiTiledPartThreading
  testMultiView
  testNativeFormat
- testOptimized
  testOptimizedInterleavePatterns
  testPartHelper
  testPreviewImage
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/main.cpp openexr-3.4.14/src/test/OpenEXRTest/main.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/main.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/main.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -47,7 +47,6 @@
 #include "testMultiTiledPartThreading.h"
 #include "testMultiView.h"
 #include "testNativeFormat.h"
-#include "testOptimized.h"
 #include "testOptimizedInterleavePatterns.h"
 #include "testPartHelper.h"
 #include "testPreviewImage.h"
@@ -204,7 +203,6 @@
     TEST (testExistingStreams, "core");
     TEST (testExistingStreamsUTF8, "core");
     TEST (testStandardAttributes, "core");
-    TEST (testOptimized, "basic");
     TEST (testOptimizedInterleavePatterns, "basic");
     TEST (testYca, "basic");
     TEST (testTiledYa, "basic");
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testCompressionApi.cpp openexr-3.4.14/src/test/OpenEXRTest/testCompressionApi.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testCompressionApi.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testCompressionApi.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -60,7 +60,7 @@
 
             assert (getCompressionNumScanlines (c) > 0);
 
-            // update this if you add a new lossy compressor.
+            // update this if you add a new compressor.
             switch (c)
             {
                 case NO_COMPRESSION:
@@ -68,6 +68,8 @@
                 case ZIPS_COMPRESSION:
                 case ZIP_COMPRESSION:
                 case PIZ_COMPRESSION:
+                case HTJ2K256_COMPRESSION:
+                case HTJ2K32_COMPRESSION:
                     assert (isLossyCompression (c) == false);
                     break;
 
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testExistingStreams.cpp openexr-3.4.14/src/test/OpenEXRTest/testExistingStreams.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testExistingStreams.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testExistingStreams.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -127,7 +127,24 @@
     , _length (0)
 {
 #ifdef _WIN32
+
+    // TODO: Remove pragmas for pushing/popping deprecation warnings when WidenFilename is removed from API
+#   ifdef _MSC_VER
+#       pragma warning(push,0)
+#       pragma warning(disable: 4996)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic push
+#       pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#   endif
+
     const std::wstring fileNameWide = WidenFilename (fileName);
+
+#   ifdef _MSC_VER
+#       pragma warning(pop)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic pop
+#   endif
+
     try
     {
         _f = CreateFileW (
@@ -1093,7 +1110,23 @@
     {
         cout << "writing";
 #ifdef _WIN32
+
+    // TODO: Remove pragmas for pushing/popping deprecation warnings when WidenFilename is removed from API
+#   ifdef _MSC_VER
+#       pragma warning(push,0)
+#       pragma warning(disable: 4996)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic push
+#       pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#   endif
+
         _wremove (WidenFilename (outfn.c_str ()).c_str ());
+
+#   ifdef _MSC_VER
+#       pragma warning(pop)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic pop
+#   endif
 #else
         remove (outfn.c_str ());
 #endif
@@ -1144,7 +1177,23 @@
     cout << endl;
 
 #ifdef _WIN32
+
+    // TODO: Remove pragmas for pushing/popping deprecation warnings when WidenFilename is removed from API
+#   ifdef _MSC_VER
+#       pragma warning(push,0)
+#       pragma warning(disable: 4996)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic push
+#       pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#   endif
+
     _wremove (WidenFilename (outfn.c_str ()).c_str ());
+
+#   ifdef _MSC_VER
+#       pragma warning(pop)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic pop
+#   endif
 #else
     remove (outfn.c_str ());
 #endif
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testMagic.cpp openexr-3.4.14/src/test/OpenEXRTest/testMagic.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testMagic.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testMagic.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -12,8 +12,15 @@
 #include <ImfVersion.h>
 #include <assert.h>
 #include <exception>
+#include <filesystem>
 #include <iostream>
 #include <stdio.h>
+#include <string>
+
+#if (defined(__cplusplus) && __cplusplus >= 202002L) \
+    || (defined(_MSVC_LANG) && _MSVC_LANG >= 202004L)
+#    include <string_view>
+#endif
 
 #include "TestUtilFStream.h"
 
@@ -85,10 +92,91 @@
          << (tiledFile ? "is tiled" : "is not tiled") << endl;
 }
 
+void
+testUtf8Filename (const std::string& tempDir)
+{
+    namespace fs = std::filesystem;
+
+    // Test isOpenExrFile on an image with a UTF-8 filename (copy a known-good
+    // .exr under a name containing non-ASCII UTF-8 bytes).
+    //
+    // isOpenExrFile() takes a const char* argument that is expected to point
+    // to a null-terminated sequence of potentially non-ASCII UTF-8 bytes.
+    // We build a std::filesystem::path from a UTF-8 file name, join it with
+    // tempDir, copy a known-good .exr to that destination, then call
+    // isOpenExrFile() with the full UTF-8 path obtained from that path
+    // object (see below).
+    //
+    // This is complicated because of how std::filesystem and UTF-8 interact
+    // across C++17 and C++20, and because isOpenExrFile takes const char*,
+    // not char8_t*.
+    //
+    // - Building fs::path from a UTF-8 std::string: In C++20, treating a
+    //   char buffer as UTF-8 for path construction is spelled with char8_t
+    //   (std::u8string_view). A reinterpret_cast from const char* to const
+    //   char8_t* is the standard way to attach that meaning to the same byte
+    //   sequence without transcoding. Pre-C++20 we use the narrow-string
+    //   constructor directly.
+    //
+    // - Getting a string for isOpenExrFile: We ask the path for its UTF-8
+    //   form via u8string() so we match the actual on-disk name the library
+    //   will open. In C++20, u8string() returns std::u8string (char8_t), but
+    //   OpenEXR's API still expects a NUL-terminated const char* UTF-8 path,
+    //   so we copy those bytes into a std::string (again via reinterpret_cast
+    //   of the data pointer) and pass c_str(). In C++17, u8string() already
+    //   returns std::string, so no second cast is needed there.
+    
+    const std::string utf8Name (
+        "openexr_isOpenExrFile_\xc3\xa9\xe6\x97\xa5_\xd1\x84.exr");
+    const fs::path src = fs::path (ILM_IMF_TEST_IMAGEDIR) / "comp_none.exr";
+#if (defined(__cplusplus) && __cplusplus >= 202002L) \
+    || (defined(_MSVC_LANG) && _MSVC_LANG >= 202004L)
+    // C++20+: interpret UTF-8 bytes as char8_t.
+    fs::path utf8Path (std::u8string_view (reinterpret_cast<const char8_t*> (utf8Name.data ()),
+                                           utf8Name.size ()));
+#else
+    fs::path utf8Path (utf8Name);
+#endif
+    const fs::path dst = fs::path (tempDir) / utf8Path;
+
+    std::error_code ec;
+    fs::copy_file (src, dst, fs::copy_options::overwrite_existing, ec);
+    if (ec)
+    {
+        cout << "skipping UTF-8 path test (could not copy " << src << " to " << dst << "): "
+             << ec.message () << endl;
+        assert(false);
+    }
+
+    // remove the temp copy on exit
+    struct Cleanup
+    {
+        fs::path p;
+        ~Cleanup ()
+        {
+            std::error_code e;
+            std::filesystem::remove (p, e);
+        }
+    } cleanup{dst};
+
+    const auto u = dst.u8string ();
+    const std::string dstUtf8 (reinterpret_cast<const char*> (u.data ()), u.size ());
+
+    bool tiled = false, deep = false, multiPart = false;
+
+    bool ok = isOpenExrFile (dstUtf8.c_str (), tiled, deep, multiPart);
+    assert (ok);
+    assert (!tiled && !deep && !multiPart);
+
+    assert (isOpenExrFile (dstUtf8.c_str ()));
+
+    cout << "UTF-8 filename isOpenExrFile ok\n";
+}
+
 } // namespace
 
 void
-testMagic (const std::string&)
+testMagic (const std::string& tempDir)
 {
     try
     {
@@ -103,6 +191,8 @@
         testFile2 (
             ILM_IMF_TEST_IMAGEDIR "does_not_exist.exr", false, false, false);
 
+        testUtf8Filename (tempDir);
+
         cout << "ok\n" << endl;
     }
     catch (const std::exception& e)
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testMultiView.cpp openexr-3.4.14/src/test/OpenEXRTest/testMultiView.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testMultiView.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testMultiView.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -111,6 +111,13 @@
         viewFromChannelName ("devon.and.cornwall.longwool", multiView) == "");
 
     //
+    // Regression test for GHSA-g8f2-r72m-48vx: empty multiView must not
+    // index element 0; dotless channels belong to no named view.
+    //
+
+    assert (viewFromChannelName ("R", StringVector ()) == "");
+
+    //
     // Test areCounterparts()
     //
 
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testOptimized.cpp openexr-3.4.14/src/test/OpenEXRTest/testOptimized.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testOptimized.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testOptimized.cpp	1970-01-01 02:00:00.000000000 +0200
@@ -1,607 +0,0 @@
-//
-// SPDX-License-Identifier: BSD-3-Clause
-// Copyright (c) Contributors to the OpenEXR Project.
-//
-
-#ifdef NDEBUG
-#    undef NDEBUG
-#endif
-
-#include "half.h"
-#include <ImfArray.h>
-#include <ImfChannelList.h>
-#include <ImfFrameBuffer.h>
-#include <ImfHeader.h>
-#include <ImfInputFile.h>
-#include <ImfOutputFile.h>
-#include <ImfVersion.h>
-
-#include <ImfBoxAttribute.h>
-#include <ImfChannelListAttribute.h>
-#include <ImfChromaticitiesAttribute.h>
-#include <ImfCompressionAttribute.h>
-#include <ImfDoubleAttribute.h>
-#include <ImfEnvmapAttribute.h>
-#include <ImfFloatAttribute.h>
-#include <ImfIntAttribute.h>
-#include <ImfLineOrderAttribute.h>
-#include <ImfMatrixAttribute.h>
-#include <ImfOpaqueAttribute.h>
-#include <ImfStringAttribute.h>
-#include <ImfStringVectorAttribute.h>
-#include <ImfVecAttribute.h>
-
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-using namespace std;
-namespace IMF = OPENEXR_IMF_NAMESPACE;
-using namespace IMF;
-
-namespace IMATH = IMATH_NAMESPACE;
-using namespace IMATH;
-
-namespace
-{
-
-static const int IMAGE_2K_HEIGHT = 1152;
-static const int IMAGE_2K_WIDTH  = 2048;
-
-static const char* CHANNEL_NAMES[]      = {"R", "G", "B", "A"};
-static const char* CHANNEL_NAMES_LEFT[] = {
-    "left.R", "left.G", "left.B", "left.A"};
-
-static const half ALPHA_DEFAULT_VALUE (1.0f);
-
-#define RGB_FILENAME "imf_optimized_aces_rgb.exr"
-#define RGBA_FILENAME "imf_optimized_aces_rgba.exr"
-#define RGB_STEREO_FILENAME "imf_optimized_aces_rgb_stereo.exr"
-#define RGBA_STEREO_FILENAME "imf_optimized_aces_rgba_stereo.exr"
-
-typedef enum EImageType
-{
-    IMAGE_TYPE_RGB   = 1,
-    IMAGE_TYPE_RGBA  = 2,
-    IMAGE_TYPE_OTHER = 3
-} EImageType;
-
-int
-getNbChannels (EImageType pImageType)
-{
-    int retVal = 0;
-
-    switch (pImageType)
-    {
-        case IMAGE_TYPE_RGB: retVal = 3; break;
-        case IMAGE_TYPE_RGBA: retVal = 4; break;
-        case IMAGE_TYPE_OTHER: retVal = 2; break;
-        default: retVal = 0; break;
-    }
-
-    return retVal;
-}
-
-//
-//
-//
-void
-generatePixel (int i, int j, half* rgbaValue, bool pIsLeft)
-{
-    float redValue   = 0.0f;
-    float greenValue = 0.0f;
-    float blueValue  = 0.0f;
-    float alphaValue = 0.0f;
-
-    // These formulas are arbitrary but generate results that vary
-    // depending on pixel position.  They are used to validate that
-    // pixels are read/written correctly, because if we had only one
-    // value for the whole image, the tests would still pass if we read
-    // only one pixel and copied it all across the buffer.
-    if (pIsLeft)
-    {
-        redValue   = ((i + j) % 10 + 0.004f * j) / 10.0f;
-        greenValue = ((j + j) % 10 + 0.006f * i) / 10.0f;
-        blueValue  = ((j * j + i) % 10 + 0.003f * j) / 10.0f;
-        alphaValue = ALPHA_DEFAULT_VALUE;
-    }
-    else
-    {
-        redValue   = ((i * j) % 10 + 0.005f * j) / 10.0f;
-        greenValue = ((i + i) % 10 + 0.007f * i) / 10.0f;
-        blueValue  = ((i * i + j) % 10 + 0.006f * j) / 10.0f;
-        alphaValue = ALPHA_DEFAULT_VALUE;
-    }
-
-    rgbaValue[0] = redValue;
-    rgbaValue[1] = greenValue;
-    rgbaValue[2] = blueValue;
-    rgbaValue[3] = alphaValue;
-}
-
-//
-// Given a buffer, fill the pixels with arbitrary but deterministic values.
-// Used to fill the pixels in a buffer before writing them to a file.
-//
-void
-fillPixels (
-    const int&     pImageHeight,
-    const int&     pImageWidth,
-    Array2D<half>& pPixels,
-    int            pNbChannels,
-    bool           pIsLeft)
-{
-    for (int i = 0; i < pImageHeight; ++i)
-    {
-        for (int j = 0; j < pImageWidth; ++j)
-        {
-            half rgbaValue[4];
-
-            generatePixel (i, j, &rgbaValue[0], pIsLeft);
-            memcpy (
-                (void*) &pPixels[i][j * pNbChannels],
-                &rgbaValue[0],
-                pNbChannels * sizeof (half));
-        }
-    }
-}
-
-//
-// Validate that the pixels value are the same as what we used to fill them.
-// Used after reading the pixels from the file to validate that it was read
-// properly.
-//
-void
-validatePixels (
-    const int&     pImageHeight,
-    const int&     pImageWidth,
-    Array2D<half>& pPixels,
-    int            pNbChannels,
-    bool           pIsLeft)
-{
-    for (int i = 0; i < pImageHeight; ++i)
-    {
-        for (int j = 0; j < pImageWidth; ++j)
-        {
-            int  retVal = -1;
-            half rgbaValue[4];
-            generatePixel (i, j, &rgbaValue[0], pIsLeft);
-
-            retVal = memcmp (
-                (void*) &pPixels[i][j * pNbChannels],
-                (void*) &rgbaValue[0],
-                pNbChannels * sizeof (half));
-
-            if (retVal != 0)
-            {
-                cout << "ERROR at pixel [" << i << ";" << j << "]" << endl;
-                cout << "\tExpected [" << rgbaValue[0] << ", " << rgbaValue[1]
-                     << ", " << rgbaValue[2] << "] " << endl;
-
-                cout << "\tReceived [" << pPixels[i][j * pNbChannels] << ", "
-                     << pPixels[i][j * pNbChannels + 1] << ", "
-                     << pPixels[i][j * pNbChannels + 2] << "]" << endl;
-                assert (retVal == 0);
-            }
-        }
-    }
-}
-
-//
-//  Write pixels to a file (mono version)
-//
-void
-writePixels (
-    const char     pFilename[],
-    const int&     pImageHeight,
-    const int&     pImageWidth,
-    Array2D<half>& pPixels,
-    int            pNbChannels,
-    Compression    pCompression)
-{
-    Header header (
-        pImageWidth,
-        pImageHeight,
-        1.0f,
-        V2f (0.0f, 0.0f),
-        1.0f,
-        INCREASING_Y,
-        pCompression);
-    for (int i = 0; i < pNbChannels; ++i)
-    {
-        header.channels ().insert (CHANNEL_NAMES[i], Channel (HALF));
-    }
-
-    OutputFile  lFile (pFilename, header);
-    FrameBuffer lOutputFrameBuffer;
-
-    for (int i = 0; i < pNbChannels; ++i)
-    {
-        lOutputFrameBuffer.insert (
-            CHANNEL_NAMES[i],
-            Slice (
-                HALF,
-                (char*) &pPixels[0][i],
-                sizeof (pPixels[0][0]) * pNbChannels,
-                sizeof (pPixels[0][0]) * pNbChannels * pImageWidth));
-    }
-
-    lFile.setFrameBuffer (lOutputFrameBuffer);
-    lFile.writePixels (pImageHeight);
-}
-
-//
-//  Write pixels to a file (stereo version)
-//
-void
-writePixels (
-    const char     pFilename[],
-    const int&     pImageHeight,
-    const int&     pImageWidth,
-    Array2D<half>& pPixels,
-    Array2D<half>& pPixelsLeft,
-    int            pNbChannels,
-    Compression    pCompression)
-{
-    Header header (
-        pImageWidth,
-        pImageHeight,
-        1.0f,
-        V2f (0.0f, 0.0f),
-        1.0f,
-        INCREASING_Y,
-        pCompression);
-    for (int i = 0; i < pNbChannels; ++i)
-    {
-        header.channels ().insert (CHANNEL_NAMES[i], Channel (HALF));
-        header.channels ().insert (CHANNEL_NAMES_LEFT[i], Channel (HALF));
-    }
-
-    StringVector multiView;
-    multiView.push_back ("right");
-    multiView.push_back ("left");
-    header.insert (
-        "multiView", IMF::TypedAttribute<IMF::StringVector> (multiView));
-
-    OutputFile  lFile (pFilename, header);
-    FrameBuffer lOutputFrameBuffer;
-
-    for (int i = 0; i < pNbChannels; ++i)
-    {
-        lOutputFrameBuffer.insert (
-            CHANNEL_NAMES[i],
-            Slice (
-                HALF,
-                (char*) &pPixels[0][i],
-                sizeof (pPixels[0][0]) * pNbChannels,
-                sizeof (pPixels[0][0]) * pNbChannels * pImageWidth));
-
-        lOutputFrameBuffer.insert (
-            CHANNEL_NAMES_LEFT[i],
-            Slice (
-                HALF,
-                (char*) &pPixelsLeft[0][i],
-                sizeof (pPixelsLeft[0][0]) * pNbChannels,
-                sizeof (pPixelsLeft[0][0]) * pNbChannels * pImageWidth));
-    }
-
-    lFile.setFrameBuffer (lOutputFrameBuffer);
-    lFile.writePixels (pImageHeight);
-}
-
-//
-//  Read pixels from a file (mono version).
-//
-void
-readPixels (const char pFilename[], int pNbChannels, Array2D<half>& pPixels)
-{
-    InputFile    lFile (pFilename);
-    IMATH::Box2i lDataWindow = lFile.header ().dataWindow ();
-
-    int lWidth = lDataWindow.max.x - lDataWindow.min.x + 1;
-
-    FrameBuffer lInputFrameBuffer;
-
-    for (int i = 0; i < pNbChannels; ++i)
-    {
-        lInputFrameBuffer.insert (
-            CHANNEL_NAMES[i],
-            Slice (
-                HALF,
-                (char*) &pPixels[0][i],
-                sizeof (pPixels[0][0]) * pNbChannels,
-                sizeof (pPixels[0][0]) * pNbChannels * lWidth,
-                1,
-                1,
-                ALPHA_DEFAULT_VALUE));
-    }
-
-    lFile.setFrameBuffer (lInputFrameBuffer);
-
-    bool is_optimized = lFile.isOptimizationEnabled ();
-    if (is_optimized)
-    {
-        cout << " optimization enabled\n";
-
-        if (pNbChannels == 2)
-        {
-            cerr << " error: isOptimizationEnabled returned TRUE, but "
-                    "optimization not known to work for two channel images\n";
-            assert (pNbChannels != 2);
-        }
-    }
-    else
-    {
-        cout << " optimization disabled\n";
-#ifdef IMF_HAVE_SSE2
-        if (pNbChannels != 2)
-        {
-            cerr << " error: isOptimizationEnabled returned FALSE, but "
-                    "should work for "
-                 << pNbChannels << "channel images\n";
-            assert (pNbChannels == 2);
-        }
-
-#endif
-    }
-
-    lFile.readPixels (lDataWindow.min.y, lDataWindow.max.y);
-}
-
-//
-//  Read pixels from a file (stereo version).
-//
-void
-readPixels (
-    const char     pFilename[],
-    int            pNbChannels,
-    Array2D<half>& pPixels,
-    Array2D<half>& pPixelsLeft)
-{
-    InputFile    lFile (pFilename);
-    IMATH::Box2i lDataWindow = lFile.header ().dataWindow ();
-
-    int lWidth = lDataWindow.max.x - lDataWindow.min.x + 1;
-
-    FrameBuffer lInputFrameBuffer;
-
-    for (int i = 0; i < pNbChannels; ++i)
-    {
-        lInputFrameBuffer.insert (
-            CHANNEL_NAMES[i],
-            Slice (
-                HALF,
-                (char*) &pPixels[0][i],
-                sizeof (pPixels[0][0]) * pNbChannels,
-                sizeof (pPixels[0][0]) * pNbChannels * lWidth,
-                1,
-                1,
-                ALPHA_DEFAULT_VALUE));
-
-        lInputFrameBuffer.insert (
-            CHANNEL_NAMES_LEFT[i],
-            Slice (
-                HALF,
-                (char*) &pPixelsLeft[0][i],
-                sizeof (pPixelsLeft[0][0]) * pNbChannels,
-                sizeof (pPixelsLeft[0][0]) * pNbChannels * lWidth,
-                1,
-                1,
-                ALPHA_DEFAULT_VALUE));
-    }
-
-    lFile.setFrameBuffer (lInputFrameBuffer);
-    lFile.readPixels (lDataWindow.min.y, lDataWindow.max.y);
-}
-
-//
-// Allocate an array of pixels, fill them with values and then write
-// them to a file.
-void
-writeFile (
-    const char  pFilename[],
-    int         pHeight,
-    int         pWidth,
-    EImageType  pImageType,
-    bool        pIsStereo,
-    Compression pCompression)
-{
-    const int     lNbChannels = getNbChannels (pImageType);
-    Array2D<half> lPixels;
-
-    lPixels.resizeErase (pHeight, pWidth * lNbChannels);
-    fillPixels (pHeight, pWidth, lPixels, lNbChannels, false);
-
-    if (pIsStereo)
-    {
-        Array2D<half> lPixelsLeft;
-
-        lPixelsLeft.resizeErase (pHeight, pWidth * lNbChannels);
-        fillPixels (pHeight, pWidth, lPixelsLeft, lNbChannels, true);
-
-        writePixels (
-            pFilename,
-            pHeight,
-            pWidth,
-            lPixels,
-            lPixelsLeft,
-            lNbChannels,
-            pCompression);
-    }
-    else
-    {
-        writePixels (
-            pFilename, pHeight, pWidth, lPixels, lNbChannels, pCompression);
-    }
-}
-
-//
-// Read pixels from a file and then validate that the values are the
-// same as what was used to fill them before writing.
-//
-void
-readValidateFile (
-    const char pFilename[],
-    int        pHeight,
-    int        pWidth,
-    EImageType pImageType,
-    bool       pIsStereo)
-{
-    const int lNbChannels = getNbChannels (pImageType);
-
-    Array2D<half> lPixels;
-    lPixels.resizeErase (pHeight, pWidth * lNbChannels);
-    //readPixels(pFilename, lNbChannels, lPixels);
-    //writePixels("pkTest.exr", pHeight, pWidth, lPixels, lNbChannels, NO_COMPRESSION);
-
-    if (pIsStereo)
-    {
-        Array2D<half> lPixelsLeft;
-        lPixelsLeft.resizeErase (pHeight, pWidth * lNbChannels);
-        readPixels (pFilename, lNbChannels, lPixels, lPixelsLeft);
-        validatePixels (pHeight, pWidth, lPixels, lNbChannels, false);
-        validatePixels (pHeight, pWidth, lPixelsLeft, lNbChannels, true);
-    }
-    else
-    {
-        readPixels (pFilename, lNbChannels, lPixels);
-        validatePixels (pHeight, pWidth, lPixels, lNbChannels, false);
-    }
-}
-
-//
-// confirm the optimization flag returns false for non-RGB files
-//
-void
-testNonOptimized (const std::string& tempDir)
-{
-    const int   pHeight  = IMAGE_2K_HEIGHT - 1;
-    const int   pWidth   = IMAGE_2K_WIDTH - 1;
-    std::string fn       = tempDir + RGB_FILENAME;
-    const char* filename = fn.c_str ();
-
-    remove (filename);
-    writeFile (
-        filename, pHeight, pWidth, IMAGE_TYPE_OTHER, false, NO_COMPRESSION);
-    readValidateFile (filename, pHeight, pWidth, IMAGE_TYPE_OTHER, false);
-    remove (filename);
-}
-
-//
-// Test all combinations of file/framebuffer
-//  RGB  file to RGB  framebuffer
-//  RGB  file to RGBA framebuffer
-//  RGBA file to RGB  framebuffer
-//  RGBA file to RGBA framebuffer
-// Given a switch that determines whether the pixels will be SSE-aligned,
-// whether the file is mono or stereo, and the compression algorithm used
-// to write the file.
-//
-void
-testAllCombinations (
-    bool               isAligned,
-    bool               isStereo,
-    Compression        pCompression,
-    const std::string& tempDir)
-{
-    std::string pRgb  = isStereo ? RGB_STEREO_FILENAME : RGB_FILENAME;
-    pRgb              = tempDir + pRgb;
-    std::string pRgba = isStereo ? RGBA_STEREO_FILENAME : RGBA_FILENAME;
-    pRgba             = tempDir + pRgba;
-
-    const char* pRgbFilename  = pRgb.c_str ();
-    const char* pRgbaFilename = pRgba.c_str ();
-
-    const int pHeight = isAligned ? IMAGE_2K_HEIGHT : IMAGE_2K_HEIGHT - 1;
-    const int pWidth  = isAligned ? IMAGE_2K_WIDTH : IMAGE_2K_WIDTH - 1;
-
-    remove (pRgbFilename);
-    remove (pRgbaFilename);
-
-    writeFile (
-        pRgbFilename, pHeight, pWidth, IMAGE_TYPE_RGB, isStereo, pCompression);
-    writeFile (
-        pRgbaFilename,
-        pHeight,
-        pWidth,
-        IMAGE_TYPE_RGBA,
-        isStereo,
-        pCompression);
-
-    cout << "\t\tRGB file to RGB framebuffer" << endl;
-    readValidateFile (pRgbFilename, pHeight, pWidth, IMAGE_TYPE_RGB, isStereo);
-
-    cout << "\t\tRGB file to RGB framebuffer" << endl;
-    readValidateFile (pRgbFilename, pHeight, pWidth, IMAGE_TYPE_RGB, isStereo);
-
-    cout << "\t\tRGB file to RGBA framebuffer" << endl;
-    readValidateFile (pRgbFilename, pHeight, pWidth, IMAGE_TYPE_RGBA, isStereo);
-
-    cout << "\t\tRGBA file to RGB framebuffer" << endl;
-    readValidateFile (pRgbaFilename, pHeight, pWidth, IMAGE_TYPE_RGB, isStereo);
-
-    cout << "\t\tRGBA file to RGBA framebuffer" << endl;
-    readValidateFile (
-        pRgbaFilename, pHeight, pWidth, IMAGE_TYPE_RGBA, isStereo);
-
-    remove (pRgbFilename);
-    remove (pRgbaFilename);
-}
-
-} // anonymous namespace
-
-void
-testOptimized (const std::string& tempDir)
-{
-    try
-    {
-        // Test all combinations
-        // Aligned file with no compression
-        // Unaligned file with no compression
-        // Aligned file with zip compression
-        // Unaligned file with zip compression
-        //
-        // Other algorithms are not necessary because we are not testing
-        // compression but whetherthe optimized readPixels() code works
-        // with a compressed file.
-        // MONO
-        cout << "\nTesting optimized code path for rgb(a) images-- "
-                "2048x1152 (alignment respected) UNCOMPRESSED"
-             << endl;
-
-        cout << "\tNON-OPTIMIZABLE file" << endl;
-        testNonOptimized (tempDir);
-
-        cout << "\tALIGNED -- MONO -- NO COMPRESSION" << endl;
-        testAllCombinations (true, false, NO_COMPRESSION, tempDir);
-
-        cout << "\tUNALIGNED -- MONO -- NO COMPRESSION" << endl;
-        testAllCombinations (false, false, NO_COMPRESSION, tempDir);
-
-        cout << "\tALIGNED -- MONO -- ZIP COMPRESSION" << endl;
-        testAllCombinations (true, false, ZIP_COMPRESSION, tempDir);
-
-        cout << "\tUNALIGNED -- MONO -- ZIP COMPRESSION" << endl;
-        testAllCombinations (false, false, ZIP_COMPRESSION, tempDir);
-
-        //// STEREO
-        cout << "\tALIGNED -- STEREO -- NO COMPRESSION" << endl;
-        testAllCombinations (true, true, NO_COMPRESSION, tempDir);
-
-        cout << "\tUNALIGNED -- STEREO -- NO COMPRESSION" << endl;
-        testAllCombinations (false, true, NO_COMPRESSION, tempDir);
-
-        cout << "\tALIGNED -- STEREO -- ZIP COMPRESSION" << endl;
-        testAllCombinations (true, true, ZIP_COMPRESSION, tempDir);
-
-        cout << "\tUNALIGNED -- STEREO -- ZIP COMPRESSION" << endl;
-        testAllCombinations (false, true, ZIP_COMPRESSION, tempDir);
-
-        cout << "RGB(A) files validation complete \n" << endl;
-    }
-    catch (const std::exception& e)
-    {
-        cerr << "ERROR -- caught exception: " << e.what () << endl;
-        assert (false);
-    }
-}
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testOptimized.h openexr-3.4.14/src/test/OpenEXRTest/testOptimized.h
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testOptimized.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testOptimized.h	1970-01-01 02:00:00.000000000 +0200
@@ -1,8 +0,0 @@
-//
-// SPDX-License-Identifier: BSD-3-Clause
-// Copyright (c) Contributors to the OpenEXR Project.
-//
-
-#include <string>
-
-void testOptimized (const std::string& tempDir);
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testOptimizedInterleavePatterns.cpp openexr-3.4.14/src/test/OpenEXRTest/testOptimizedInterleavePatterns.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testOptimizedInterleavePatterns.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testOptimizedInterleavePatterns.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -45,9 +45,8 @@
 vector<char>
     preReadBuffer; // buffer as it was before reading - unread, unfilled channels should be unchanged
 
-int gOptimisedReads = 0;
-int gSuccesses      = 0;
-int gFailures       = 0;
+int gSuccesses = 0;
+int gFailures  = 0;
 
 //
 // @todo Needs a description of what this is used for.
@@ -537,7 +536,7 @@
     return hdr.dataWindow ();
 }
 
-bool
+void
 readfile (
     Schema       scheme,
     FrameBuffer& buf,     ///< list of channels to read: index to readingBuffer
@@ -564,8 +563,6 @@
     infile.readPixels (
         infile.header ().dataWindow ().min.y,
         infile.header ().dataWindow ().max.y);
-
-    return infile.isOptimizationEnabled ();
 }
 
 void
@@ -585,7 +582,7 @@
     FrameBuffer preReadFrameBuf;
     FrameBuffer postReadFrameBuf;
     cout.flush ();
-    bool opt = readfile (
+    readfile (
         readScheme,
         readFrameBuf,
         preReadFrameBuf,
@@ -594,13 +591,7 @@
     if (compare (readFrameBuf, writeFrameBuf, dw, nonfatal) &&
         compare (preReadFrameBuf, postReadFrameBuf, dw, nonfatal))
     {
-        cout << " OK ";
-        if (opt)
-        {
-            cout << "OPTIMISED ";
-            gOptimisedReads++;
-        }
-        cout << "\n";
+        cout << " OK\n";
         gSuccesses++;
     }
     else
@@ -618,9 +609,8 @@
     int i       = 0;
     int skipped = 0;
 
-    gFailures       = 0;
-    gSuccesses      = 0;
-    gOptimisedReads = 0;
+    gFailures  = 0;
+    gSuccesses = 0;
 
     while (Schemes[i]._name != NULL)
     {
@@ -645,7 +635,6 @@
 
     cout << gFailures << '/' << (gSuccesses + gFailures) << " runs failed\n";
     cout << skipped << " tests skipped (assumed to be bad)\n";
-    cout << gOptimisedReads << '/' << gSuccesses << " optimised\n";
 
     if (gFailures > 0)
     {
@@ -662,12 +651,12 @@
     filename = tempDir + "imf_test_interleave_patterns.exr";
 
     cout
-        << "Testing SSE optimisation with different interleave patterns (large images) ... "
+        << "Testing different interleave patterns (large images) ... "
         << endl;
     runtests (false, false);
 
     cout
-        << "Testing SSE optimisation with different interleave patterns (tiny images) ... "
+        << "Testing different interleave patterns (tiny images) ... "
         << endl;
     runtests (false, true);
 
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/testStandardAttributes.cpp openexr-3.4.14/src/test/OpenEXRTest/testStandardAttributes.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRTest/testStandardAttributes.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/testStandardAttributes.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -899,14 +899,27 @@
     assert (hasChromaticities (header) == false);
     assert (hasWhiteLuminance (header) == false);
     assert (hasAdoptedNeutral (header) == false);
-    assert (hasRenderingTransform (header) == false);
-    assert (hasLookModTransform (header) == false);
     assert (hasEnvmap (header) == false);
     assert (hasWrapmodes (header) == false);
     assert (hasMultiView (header) == false);
     assert (hasDeepImageState (header) == false);
     assert (hasIDManifest (header) == false);
     assert (hasColorInteropID (header) == false);
+
+#if defined(_MSC_VER)
+    __pragma(warning(push))
+    __pragma(warning(disable: 4996))
+#elif defined(__clang__) || defined(__GNUC__)
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+    assert (hasRenderingTransform (header) == false);
+    assert (hasLookModTransform (header) == false);
+#if defined(_MSC_VER)
+    __pragma(warning(pop))
+#elif defined(__clang__) || defined(__GNUC__)
+    #pragma GCC diagnostic pop
+#endif
 }
 
 void
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRTest/TestUtilFStream.h openexr-3.4.14/src/test/OpenEXRTest/TestUtilFStream.h
--- openexr-3.4.6+ds/src/test/OpenEXRTest/TestUtilFStream.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRTest/TestUtilFStream.h	2026-08-05 04:29:55.000000000 +0300
@@ -103,8 +103,26 @@
 OpenStreamWithUTF8Name (
     StreamType& is, const char* filename, std::ios_base::openmode mode)
 {
-#    ifdef _WIN32
+
+#   ifdef _WIN32
+
+// TODO: Remove pragmas for pushing/popping deprecation warnings when WidenFilename is removed from API
+#   ifdef _MSC_VER
+#       pragma warning(push)
+#       pragma warning(disable: 4996)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic push
+#       pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#   endif
+
     std::wstring wfn = OPENEXR_IMF_INTERNAL_NAMESPACE::WidenFilename (filename);
+
+#   ifdef _MSC_VER
+#       pragma warning(pop)
+#   elif defined(__clang__) || defined(__GNUC__)
+#       pragma GCC diagnostic pop
+#   endif
+
 #        ifdef USE_WIDEN_FILEBUF
     using CharT   = typename StreamType::char_type;
     using TraitsT = typename StreamType::traits_type;
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRUtilTest/CMakeLists.txt openexr-3.4.14/src/test/OpenEXRUtilTest/CMakeLists.txt
--- openexr-3.4.6+ds/src/test/OpenEXRUtilTest/CMakeLists.txt	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRUtilTest/CMakeLists.txt	2026-08-05 04:29:55.000000000 +0300
@@ -9,6 +9,8 @@
   testDeepImage.h
   testIO.cpp
   testIO.h
+  testImageChannel.cpp
+  testImageChannel.h
  )
 target_include_directories(OpenEXRUtilTest PRIVATE ../OpenEXRTest)
 target_link_libraries(OpenEXRUtilTest OpenEXR::OpenEXRUtil)
@@ -35,4 +37,5 @@
   testFlatImage
   testDeepImage
   testIO
+  testImageChannel
 )
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRUtilTest/main.cpp openexr-3.4.14/src/test/OpenEXRUtilTest/main.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRUtilTest/main.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRUtilTest/main.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -12,6 +12,7 @@
 
 #include "testDeepImage.h"
 #include "testFlatImage.h"
+#include "testImageChannel.h"
 #include "testIO.h"
 #include "tmpDir.h"
 #include <ImathRandom.h>
@@ -89,6 +90,7 @@
     TEST (testFlatImage);
     TEST (testDeepImage);
     TEST (testIO);
+    TEST (testImageChannel);
     // NB: If you add a test here, make sure to enumerate it in the
     // CMakeLists.txt so it runs as part of the test suite
 
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testDeepImage.cpp openexr-3.4.14/src/test/OpenEXRUtilTest/testDeepImage.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testDeepImage.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRUtilTest/testDeepImage.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -14,6 +14,7 @@
 #include <ImfHeader.h>
 
 #include <cassert>
+#include <climits>
 #include <cstdio>
 
 using namespace OPENEXR_IMF_NAMESPACE;
@@ -451,6 +452,65 @@
 }
 
 void
+testSetSampleCountRowOffset ()
+{
+    //
+    // Regression test for GHSA-54cp-3rq6-7mq8: row-based sample count setter
+    // must use dataWindow.min.y (not min.x) when mapping row index to Y.
+    //
+
+    cout << "sample count row setter with non-zero data window origin"
+         << endl;
+
+    DeepImage img;
+    img.insertChannel ("Z", FLOAT, 1, 1, false);
+
+    const Box2i dataWindow (V2i (0, 1), V2i (1, 1));
+    img.resize (dataWindow, ONE_LEVEL, ROUND_DOWN);
+
+    SampleCountChannel& sc = img.level (0).sampleCounts ();
+
+    unsigned int counts[2] = {1, 1};
+    sc.set (0, counts);
+
+    assert (sc.at (0, 1) == 1);
+    assert (sc.at (1, 1) == 1);
+}
+
+void
+testDeepChannelRowOffset ()
+{
+    //
+    // Regression test for GHSA-hwmv-39v6-739m: row() is 0-based relative
+    // to the data window and must not use the _base pointer offset.
+    //
+
+    cout << "deep channel row() with non-zero data window origin" << endl;
+
+    DeepImage img;
+    img.insertChannel ("Z", FLOAT, 1, 1, false);
+
+    const Box2i dataWindow (V2i (1024, -1024), V2i (1031, -1017));
+    img.resize (dataWindow, ONE_LEVEL, ROUND_DOWN);
+
+    DeepImageLevel&               level   = img.level (0);
+    TypedDeepImageChannel<float>& channel = level.typedChannel<float> ("Z");
+    SampleCountChannel&           sc      = level.sampleCounts ();
+
+    assert (channel.row (0)[0] ==
+            channel.at (dataWindow.min.x, dataWindow.min.y));
+    assert (&sc.row (0)[0] == &sc.at (dataWindow.min.x, dataWindow.min.y));
+
+    const int lastRow = channel.pixelsPerColumn () - 1;
+    const int lastCol = channel.pixelsPerRow () - 1;
+
+    assert (channel.row (lastRow)[lastCol] ==
+            channel.at (dataWindow.max.x, dataWindow.max.y));
+    assert (&sc.row (lastRow)[lastCol] ==
+            &sc.at (dataWindow.max.x, dataWindow.max.y));
+}
+
+void
 testShiftPixels ()
 {
     cout << "pixel shifting" << endl;
@@ -671,6 +731,46 @@
     assert (caught);
 }
 
+void
+testRoundListSizeUpLimits ()
+{
+    cout << "            roundListSizeUp limits" << endl;
+
+    auto expectArgExc = [] (auto&& fn) {
+        bool caught = false;
+        try
+        {
+            fn ();
+        }
+        catch (const ArgExc&)
+        {
+            caught = true;
+        }
+        assert (caught);
+    };
+
+    expectArgExc ([&] {
+        DeepImage img;
+        img.resize (Box2i (V2i (0, 0), V2i (0, 0)), ONE_LEVEL, ROUND_DOWN);
+        img.level (0).sampleCounts ().set (0, 0, UINT_MAX);
+    });
+
+    expectArgExc ([&] {
+        DeepImage img;
+        img.resize (Box2i (V2i (0, 0), V2i (0, 0)), ONE_LEVEL, ROUND_DOWN);
+        img.level (0).sampleCounts ().set (0, 0, 0x80000001u);
+    });
+
+    expectArgExc ([&] {
+        DeepImage img;
+        img.resize (Box2i (V2i (0, 0), V2i (0, 0)), ONE_LEVEL, ROUND_DOWN);
+        SampleCountChannel& samples = img.level (0).sampleCounts ();
+        unsigned int*       counts  = samples.beginEdit ();
+        counts[0]                   = UINT_MAX;
+        samples.endEdit ();
+    });
+}
+
 } // namespace
 
 void
@@ -683,6 +783,9 @@
         testScanLineImages (tempDir + "deepScanLines.exr");
         testTiledImages (tempDir + "deepTiles.exr");
         testSetSampleCounts ();
+        testSetSampleCountRowOffset ();
+        testDeepChannelRowOffset ();
+        testRoundListSizeUpLimits ();
         testShiftPixels ();
         testCropping (tempDir + "deepCropped.exr");
         testRenameChannel ();
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testFlatImage.cpp openexr-3.4.14/src/test/OpenEXRUtilTest/testFlatImage.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testFlatImage.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRUtilTest/testFlatImage.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -14,6 +14,7 @@
 #include <ImfHeader.h>
 
 #include <cassert>
+#include <climits>
 #include <cstdio>
 
 using namespace OPENEXR_IMF_NAMESPACE;
@@ -571,6 +572,29 @@
     assert (caught);
 }
 
+void
+testResizeInvalidDataWindow ()
+{
+    cout << "    resize with invalid data window" << endl;
+
+    FlatImage img;
+    img.insertChannel ("R", HALF);
+
+    bool caught = false;
+    try
+    {
+        img.resize (
+            Box2i (V2i (1, INT_MIN), V2i (1, 1)), ONE_LEVEL, ROUND_DOWN);
+    }
+    catch (const std::exception&)
+    {
+        caught = true;
+    }
+    assert (caught);
+
+    img.resize (Box2i (V2i (0, 0), V2i (1, 1)), ONE_LEVEL, ROUND_DOWN);
+}
+
 } // namespace
 
 void
@@ -586,6 +610,7 @@
         testCropping (tempDir + "cropped.exr");
         testRenameChannel ();
         testRenameChannels ();
+        testResizeInvalidDataWindow ();
 
         cout << "ok\n" << endl;
     }
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testImageChannel.cpp openexr-3.4.14/src/test/OpenEXRUtilTest/testImageChannel.cpp
--- openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testImageChannel.cpp	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRUtilTest/testImageChannel.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,82 @@
+//
+// SPDX-License-Identifier: BSD-3-Clause
+// Copyright (c) Contributors to the OpenEXR Project.
+//
+
+#ifdef NDEBUG
+#    undef NDEBUG
+#endif
+
+#include "testImageChannel.h"
+
+#include "ImfFlatImage.h"
+
+#include <Iex.h>
+#include <ImathBox.h>
+
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <string>
+
+using namespace OPENEXR_IMF_NAMESPACE;
+using namespace IMATH_NAMESPACE;
+using namespace IEX_NAMESPACE;
+using namespace std;
+
+namespace
+{
+
+//
+// Sampling must be >= 1. ImageChannel::resize() rejects non-positive
+// sampling before any modulo or division (so zero cannot invoke undefined
+// behavior). Other invalid cases are still caught later in resize().
+//
+
+void
+expectArgExcOnInsert (const char* label, int xSampling, int ySampling)
+{
+    FlatImage img (Box2i (V2i (0, 0), V2i (3, 3)));
+
+    try
+    {
+        img.insertChannel ("bad", HALF, xSampling, ySampling, false);
+        cerr << "ERROR -- " << label << ": expected ArgExc, insert succeeded"
+             << endl;
+        assert (false);
+    }
+    catch (const ArgExc& e)
+    {
+        // Thrown at the start of ImageChannel::resize() so % and / never use
+        // a zero sampling rate (undefined behavior).
+        assert (strstr (e.what (), "at least 1") != nullptr);
+    }
+}
+
+} // namespace
+
+void
+testImageChannel (const string& tempDir)
+{
+    (void) tempDir;
+
+    try
+    {
+        cout << "Testing ImageChannel sampling constraints" << endl;
+
+        expectArgExcOnInsert ("negative xSampling", -1, 1);
+        expectArgExcOnInsert ("negative ySampling", 1, -1);
+        expectArgExcOnInsert ("negative x and y sampling", -1, -1);
+
+        expectArgExcOnInsert ("zero xSampling", 0, 1);
+        expectArgExcOnInsert ("zero ySampling", 1, 0);
+        expectArgExcOnInsert ("zero x and y sampling", 0, 0);
+
+        cout << "ok\n" << endl;
+    }
+    catch (const std::exception& e)
+    {
+        cerr << "ERROR -- caught exception: " << e.what () << endl;
+        assert (false);
+    }
+}
diff -Nru openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testImageChannel.h openexr-3.4.14/src/test/OpenEXRUtilTest/testImageChannel.h
--- openexr-3.4.6+ds/src/test/OpenEXRUtilTest/testImageChannel.h	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/src/test/OpenEXRUtilTest/testImageChannel.h	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,8 @@
+//
+// SPDX-License-Identifier: BSD-3-Clause
+// Copyright (c) Contributors to the OpenEXR Project.
+//
+
+#include <string>
+
+void testImageChannel (const std::string& tempDir);
diff -Nru openexr-3.4.6+ds/src/wrappers/python/PyOpenEXR.cpp openexr-3.4.14/src/wrappers/python/PyOpenEXR.cpp
--- openexr-3.4.6+ds/src/wrappers/python/PyOpenEXR.cpp	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/wrappers/python/PyOpenEXR.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -55,6 +55,7 @@
 #include <ImfTimeCodeAttribute.h>
 #include <ImfVecAttribute.h>
 
+#include <algorithm>
 #include <typeinfo>
 #include <sys/types.h>
 
@@ -103,6 +104,7 @@
     : _header_only(false)
 {
 }
+
     
 //
 // Create a PyFile out of a list of parts (i.e. a multi-part file)
@@ -205,22 +207,41 @@
         
             std::vector<size_t> shape ({height, width});
 
+            if (!separate_channels)
+                P.validateCoalescedChannels (header.channels (), rgbaChannels);
+
             //
             // Read the channel data, different for image vs. deep
             //
         
             auto type = header.type();
-            if (type == SCANLINEIMAGE || type == TILEDIMAGE)
+            try
+            {
+                if (type == SCANLINEIMAGE || type == TILEDIMAGE)
+                {
+                    P.readPixels(*_inputFile, header.channels(), shape, rgbaChannels, dw, separate_channels);
+                }
+                else if (type == DEEPSCANLINE || type == DEEPTILE)
+                {
+                    P.readDeepPixels(*_inputFile, type, header.channels(), shape, rgbaChannels, dw, separate_channels);
+                }
+                parts.append(py::cast<PyPart>(PyPart(P)));
+            }
+            catch (const std::invalid_argument&)
             {
-                P.readPixels(*_inputFile, header.channels(), shape, rgbaChannels, dw, separate_channels);
+                throw;
             }
-            else if (type == DEEPSCANLINE || type == DEEPTILE)
+            catch (const std::exception& e)
             {
-                P.readDeepPixels(*_inputFile, type, header.channels(), shape, rgbaChannels, dw, separate_channels);
+                // Log the error and skip appending this part
+                py::print("Warning: Exception raised reading pixel data for part", part_index, "-", e.what());
             }
         }
-        
-        parts.append(py::cast<PyPart>(PyPart(P)));
+        else
+        {
+            // If only reading the header, always append this part
+            parts.append(py::cast<PyPart>(PyPart(P)));
+        }
     } // for parts
 }
 
@@ -229,6 +250,9 @@
                    const std::vector<size_t>& shape, const std::set<std::string>& rgbaChannels,
                    const Box2i& dw, bool separate_channels)
 {
+    if (!separate_channels)
+        validateCoalescedChannels (channel_list, rgbaChannels);
+
     FrameBuffer frameBuffer;
 
     for (auto c = channel_list.begin(); c != channel_list.end(); c++)
@@ -259,11 +283,21 @@
             std::vector<size_t> c_shape = shape;
 
             //
+            // For subsampled channels, shrink the allocated array to
+            // the actual sampled dimensions so no stale rows/columns
+            // are returned to Python.
+            //
+            if (C.xSampling > 1)
+                c_shape[1] = (shape[1] - 1) / C.xSampling + 1;
+            if (C.ySampling > 1)
+                c_shape[0] = (shape[0] - 1) / C.ySampling + 1;
+
+            //
             // If this channel belongs to one of the rgba's, give
             // the PyChannel the extra dimension and the proper shape.
             // nrgba is 3 for RGB and 4 for RGBA.
             //
-            
+
             if (rgbaChannels.find(c.name()) != rgbaChannels.end())
                 c_shape.push_back(nrgba);
 
@@ -322,7 +356,10 @@
             }
         }
 
-        size_t yStride = xStride * shape[1] / C.xSampling;
+        size_t sampledWidth = (C.xSampling > 1)
+            ? (shape[1] - 1) / C.xSampling + 1
+            : shape[1];
+        size_t yStride = xStride * sampledWidth;
 
         frameBuffer.insert (c.name(),
                             Slice::Make (c.channel().type,
@@ -433,11 +470,12 @@
         size_t channel_offset = 0;
         if (C._nrgba > 0)
         {
-            if (!strcmp(c.name(), "G"))
+            char last = c.name()[strlen(c.name()) - 1];
+            if (last == 'G')
                 channel_offset = 1;
-            else if (!strcmp(c.name(), "B"))
+            else if (last == 'B')
                 channel_offset = 2;
-            else if (!strcmp(c.name(), "A"))
+            else if (last == 'A')
                 channel_offset = 3;
         }
 
@@ -481,6 +519,9 @@
                        const std::vector<size_t>& shape, const std::set<std::string>& rgbaChannels,
                        const Box2i& dw, bool separate_channels)
 {
+    if (!separate_channels)
+        validateCoalescedChannels (channel_list, rgbaChannels);
+
     size_t width  = dw.max.x - dw.min.x + 1;
     size_t height = dw.max.y - dw.min.y + 1;
     auto dw_offset = dw.min.y * width + dw.min.x;
@@ -939,9 +980,101 @@
 // channel_name is returned as the single character name of the channel
 //
 
+namespace
+{
+
+const char*
+pixelTypeName (PixelType type)
+{
+    switch (type)
+    {
+        case UINT: return "UINT";
+        case HALF: return "HALF";
+        case FLOAT: return "FLOAT";
+        default: return "unknown";
+    }
+}
+
+} // namespace
+
+//
+// Only combine RGB(A) channels into a single numpy array when coalescing is
+// unambiguous: all channels in a group must share the same pixel type, and no
+// literal channel name may collide with a coalesced group key (for example,
+// literal "left" plus "left.R", "left.G", "left.B", each of which would
+// attempt to add a channel dictionary entry with key "left"). 
+//
+// Note that attempting to coalesce invalid channel layouts causes a failure of
+// the entire file read, whereas other read errors simply skip the offending
+// part. This is reasonable behavior since the condition is not a defect in
+// the data itself, but simply an inability to return the data in the format
+// the user requested.
+
+void
+PyPart::validateCoalescedChannels (
+    const ChannelList&           channel_list,
+    const std::set<std::string>& rgbaChannels) const
+{
+    if (rgbaChannels.empty ())
+        return;
+
+    std::map<std::string, PixelType> groupType;
+    std::set<std::string>            coalescedKeys;
+
+    for (auto c = channel_list.begin (); c != channel_list.end (); ++c)
+    {
+        if (rgbaChannels.find (c.name ()) == rgbaChannels.end ())
+            continue;
+
+        // py_channel_name is the name of the combined channel, i.e. if the
+        // file has left.R, left.G, left.B, then py_channel_name is "left".
+        // It's allowable to have "left" be FLOAT and "right" be HALF, but
+        // all RGB channels within "left" and "right" must have the same
+        // type.
+
+        std::string py_channel_name;
+        char        channel_name;
+        if (channelNameToRGBA (channel_list, c.name (), py_channel_name, channel_name) <= 0)
+            continue;
+
+        coalescedKeys.insert (py_channel_name);
+
+        const PixelType channelType = c.channel ().type;
+        auto            it          = groupType.find (py_channel_name);
+        if (it == groupType.end ())
+            groupType[py_channel_name] = channelType;
+        else if (it->second != channelType)
+        {
+            std::stringstream err;
+            err << "cannot coalesce channels into \"" << py_channel_name
+                << "\": channel \"" << c.name () << "\" has pixel type "
+                << pixelTypeName (channelType) << " but other channels in the group "
+                << "have pixel type " << pixelTypeName (it->second)
+                << "; use separate_channels=True";
+            throw std::invalid_argument (err.str ());
+        }
+    }
+
+    for (auto c = channel_list.begin (); c != channel_list.end (); ++c)
+    {
+        if (rgbaChannels.find (c.name ()) != rgbaChannels.end ())
+            continue;
+
+        if (coalescedKeys.find (c.name ()) != coalescedKeys.end ())
+        {
+            std::stringstream err;
+            err << "cannot coalesce channels into \"" << c.name ()
+                << "\": channel \"" << c.name ()
+                << "\" collides with a coalesced RGB/RGBA group key"
+                << "; use separate_channels=True";
+            throw std::invalid_argument (err.str ());
+        }
+    }
+}
+
 int
 PyPart::channelNameToRGBA(const ChannelList& channel_list, const std::string& name,
-                          std::string& py_channel_name, char& channel_name)
+                          std::string& py_channel_name, char& channel_name) const
 {
     py_channel_name = name;
     channel_name = py_channel_name.back();
@@ -1065,14 +1198,14 @@
         
         Header header;
 
-        if (P.name().empty() && parts.size() > 1)
+        if (!P.name().empty())
+            header.setName (P.name());
+	else if (parts.size() > 1)
         {
             std::stringstream n;
             n << "Part" << part_index;
             header.setName (n.str());
         }
-        else
-            header.setName (P.name());
 
         //
         // Add attributes from the py::dict to the output header
@@ -1189,7 +1322,44 @@
                                                                C.pLinear));
             }
         }
-        
+
+        //
+        // When writing from numpy channel buffers, the dataWindow size must
+        // match the pixel array extent (shape[0] = height, shape[1] = width).
+        // Otherwise Slice setup disagrees with the buffer and native code can
+        // fault. Skip when copying pixels from an input file, when there are
+        // no channel buffers, or when any channel uses subsampling (different
+        // size rule than full-res arrays).
+        //
+        if (!(_header_only && _inputFile) && !P.channels.empty())
+        {
+            const bool anySubsampling =
+                std::any_of (P.channels.begin(),
+                             P.channels.end(),
+                             [] (const auto& kv)
+                             {
+                                 const auto& ch = kv.second.template cast<const PyChannel&>();
+                                 return ch.xSampling != 1 || ch.ySampling != 1;
+                             });
+
+            if (!anySubsampling)
+            {
+                const Box2i& dw = header.dataWindow();
+                const int dwW  = dw.max.x - dw.min.x + 1;
+                const int dwH = dw.max.y - dw.min.y + 1;
+
+                const V2i dims = P.shape();
+                if (dims[0] != dwH || dims[1] != dwW)
+                {
+                    std::stringstream err;
+                    err << "dataWindow size (" << dwW << " x " << dwH
+                        << ") does not match pixel array size (" << dims[1]
+                        << " x " << dims[0] << ")";
+                    throw std::invalid_argument(err.str());
+                }
+            }
+        }
+
         headers.push_back (header);
     }
     
@@ -1602,7 +1772,22 @@
     
     return py::none();
 }
-    
+
+// Static helper functions to cache NumPy types and avoid repeated imports
+py::object
+numpy_integer ()
+{
+    static py::object type = py::module::import ("numpy").attr ("integer");
+    return type;
+}
+
+py::object
+numpy_floating ()
+{
+    static py::object type = py::module::import ("numpy").attr ("floating");
+    return type;
+}
+
 template <class P, class T>
 bool
 objectToV2(const py::object& object, Vec2<T>& v)
@@ -1610,13 +1795,42 @@
     if (py::isinstance<py::tuple>(object))
     {
         auto tup = object.cast<py::tuple>();
-        if (tup.size() == 2 &&
-            py::isinstance<P>(tup[0]) &&
-            py::isinstance<P>(tup[1]))
-        {       
-            v.x = P(tup[0]);
-            v.y = P(tup[1]);
-            return true;
+        if (tup.size() == 2)
+        {
+            // 1. Standard Python types only
+            if (py::isinstance<P> (tup[0]) && py::isinstance<P> (tup[1]))
+            {
+                v.x = P (tup[0]);
+                v.y = P (tup[1]);
+                return true;
+            }
+
+            // 2. Numpy scalar types included
+
+            // Assigning numpy equivalent to Python type P passed from the calling function
+            // objectToV2 is currently instantiated only with py::int_ and py::float_, so the ternary
+            // maps directly to numpy.integer / numpy.floating.
+            py::object target_type = std::is_same_v<P, py::int_>
+                                         ? numpy_integer ()
+                                         : numpy_floating ();
+
+            // Allowing tuples that contain numpy scalars
+            if ((py::isinstance<P> (tup[0]) ||
+                 py::isinstance (tup[0], target_type)) &&
+                (py::isinstance<P> (tup[1]) ||
+                 py::isinstance (tup[1], target_type)))
+            {
+                try
+                {
+                    v.x = py::cast<T> (tup[0]);
+                    v.y = py::cast<T> (tup[1]);
+                    return true;
+                }
+                catch (const py::cast_error&)
+                {
+                    return false;
+                }
+            }
         }
     }
     else if (py::isinstance<py::array_t<T>>(object))
@@ -1827,6 +2041,36 @@
     return false;
 }
 
+template <class T>
+bool
+objectToFloatVectorArray(const py::object& object, std::vector<float>& v)
+{
+    if (!py::isinstance<py::array_t<T>>(object))
+        return false;
+
+    auto a = object.cast<py::array_t<T>>();
+    if (a.ndim() != 1 || a.size() == 0)
+        return false;
+
+    py::buffer_info buf = a.request();
+    auto data = static_cast<const char*>(buf.ptr);
+
+    v.reserve(a.size());
+    for (Py_ssize_t i = 0; i < a.size(); ++i)
+    {
+        auto value = reinterpret_cast<const T*>(data + i * buf.strides[0]);
+        v.push_back(static_cast<float>(*value));
+    }
+
+    return true;
+}
+
+bool
+objectToFloatVector(const py::object& object, std::vector<float>& v)
+{
+    return objectToFloatVectorArray<float>(object, v);
+}
+
 void
 PyFile::insertAttribute(Header& header, const std::string& name, const py::object& object)
 {
@@ -1988,6 +2232,13 @@
         return;
     }
 
+    std::vector<float> floatVector;
+    if (objectToFloatVector(object, floatVector))
+    {
+        header.insert(name, FloatVectorAttribute(floatVector));
+        return;
+    }
+
     //
     // Inspect the rhs type
     //
@@ -2583,383 +2834,311 @@
     // The File API: Channel, Part, and File
     //
     
-    py::class_<PyChannel>(m, "Channel", R"pbdoc(
-         The class object representing a channel in an EXR image file.
-         Example
-         -------  
-         >>> import OpenEXR
-         >>> f = OpenEXR.File("image.exr")
-         >>> f.channels()['A']
-         Channel("A", xSampling=1, ySampling=1)
-    )pbdoc")
+    py::class_<PyChannel>(m, "Channel", "The class object representing a channel in an EXR image file.\n"
+                                        "Example\n"
+                                        "-------  \n"
+                                        ">>> import OpenEXR\n"
+                                        ">>> f = OpenEXR.File(\"image.exr\")\n"
+                                        ">>> f.channels()['A']\n"
+                                        "Channel(\"A\", xSampling=1, ySampling=1)")
         .def(py::init(),
-             R"pbdoc(
-             Construct an empty Channel object.
-             )pbdoc")
+             "Construct an empty Channel object.")
         .def(py::init<int,int,bool>(),
              py::arg("xSampling"),
              py::arg("ySampling"),
              py::arg("pLinear")=false,
-             R"pbdoc(
-             Construct Channel object with the given parameters.
-
-             Parameters:
-                 int : xSampling
-                     The x subsampling value
-                 int : ySampling
-                     The y subsampling value
-                 int : pLinear
-                     The pLinear value
-             )pbdoc")
+             "Construct Channel object with the given parameters.\n"
+             "\n"
+             "Parameters:\n"
+             "    int : xSampling\n"
+             "        The x subsampling value\n"
+             "    int : ySampling\n"
+             "        The y subsampling value\n"
+             "    int : pLinear\n"
+             "        The pLinear value")
         .def(py::init<py::array>(),
              py::arg("pixels"),
-             R"pbdoc(
-             Construct Channel object with the given pixel array
-
-             Parameters:
-                 np.array : pixels
-                     The numpy array of pixels. Supported types are uin32, float16, float32
-             )pbdoc")
+             "Construct Channel object with the given pixel array\n"
+             "\n"
+             "Parameters:\n"
+             "    np.array : pixels\n"
+             "        The numpy array of pixels. Supported types are uin32, float16, float32")
         .def(py::init<py::array,int,int,bool>(),
              py::arg("pixels"),
              py::arg("xSampling"),
              py::arg("ySampling"),
              py::arg("pLinear")=false,
-             R"pbdoc(
-             Construct Channel object with the given parameters.
-
-             Parameters:
-             -----------
-             np.array : pixels
-                 The numpy array of pixels. Supported types are uin32, float16, float32
-             int : xSampling
-                 The x subsampling value
-             int : ySampling
-                 The y subsampling value
-             int : pLinear
-                 The pLinear value
-             )pbdoc")
+             "Construct Channel object with the given parameters.\n"
+             "\n"
+             "Parameters:\n"
+             "-----------\n"
+             "np.array : pixels\n"
+             "    The numpy array of pixels. Supported types are uin32, float16, float32\n"
+             "int : xSampling\n"
+             "    The x subsampling value\n"
+             "int : ySampling\n"
+             "    The y subsampling value\n"
+             "int : pLinear\n"
+             "    The pLinear value")
         .def(py::init<const char*>(),
              py::arg("name"),
-             R"pbdoc(
-             Construct Channel object with the given name.
-
-             Parameters:
-             -----------
-             str : name
-                 The name of the channel.
-             )pbdoc")
+             "Construct Channel object with the given name.\n"
+             "\n"
+             "Parameters:\n"
+             "-----------\n"
+             "str : name\n"
+             "    The name of the channel.")
         .def(py::init<const char*,int,int,bool>(),
              py::arg("name"),
              py::arg("xSampling"),
              py::arg("ySampling"),
              py::arg("pLinear")=false,
-             R"pbdoc(
-             Construct Channel object with the given parameters.
-
-             Parameters:
-             -----------
-             str : name
-                 The name of the channel.
-             int : xSampling
-                 The x subsampling value
-             int : ySampling
-                 The y subsampling value
-             int : pLinear
-                 The pLinear value
-             )pbdoc")
+             "Construct Channel object with the given parameters.\n"
+             "\n"
+             "Parameters:\n"
+             "-----------\n"
+             "str : name\n"
+             "    The name of the channel.\n"
+             "int : xSampling\n"
+             "    The x subsampling value\n"
+             "int : ySampling\n"
+             "    The y subsampling value\n"
+             "int : pLinear\n"
+             "    The pLinear value")
         .def(py::init<const char*,py::array>(),
              py::arg("name"),
              py::arg("pixels"),
-             R"pbdoc(
-             Construct Channel object with the given parameters.
-
-             Parameters:
-             -----------
-             str : name
-                 The name of the channel.
-             np.array : pixels
-                 The numpy array of pixels. Supported types are uin32, float16, float32
-             )pbdoc")
+             "Construct Channel object with the given parameters.\n"
+             "\n"
+             "Parameters:\n"
+             "-----------\n"
+             "str : name\n"
+             "    The name of the channel.\n"
+             "np.array : pixels\n"
+             "    The numpy array of pixels. Supported types are uin32, float16, float32")
         .def(py::init<const char*,py::array,int,int,bool>(),
              py::arg("name"),
              py::arg("pixels"),
              py::arg("xSampling"),
              py::arg("ySampling"),
              py::arg("pLinear")=false,
-             R"pbdoc(
-             Construct Channel object with the given parameters.
-
-             Parameters:
-             -----------
-             str : name
-                 The name of the channel.
-             np.array : pixels
-                 The numpy array of pixels. Supported types are uin32, float16, float32
-             int : xSampling
-                 The x subsampling value
-             int : ySampling
-                 The y subsampling value
-             int : pLinear
-                 The pLinear value
-             )pbdoc")
+             "Construct Channel object with the given parameters.\n"
+             "\n"
+             "Parameters:\n"
+             "-----------\n"
+             "str : name\n"
+             "    The name of the channel.\n"
+             "np.array : pixels\n"
+             "    The numpy array of pixels. Supported types are uin32, float16, float32\n"
+             "int : xSampling\n"
+             "    The x subsampling value\n"
+             "int : ySampling\n"
+             "    The y subsampling value\n"
+             "int : pLinear\n"
+             "    The pLinear value")
         .def("__repr__", [](const PyChannel& c) { return repr(c); })
         .def_readwrite("name", &PyChannel::name,
-             R"pbdoc(
-              str : The channel name.
-             )pbdoc")
+             "str : The channel name.")
         .def("type", &PyChannel::pixelType,
-             R"pbdoc(
-              OpenEXR.PixelType : The pixel type (UINT, HALF, FLOAT)
-             )pbdoc")
+             "OpenEXR.PixelType : The pixel type (UINT, HALF, FLOAT)")
         .def_readwrite("xSampling", &PyChannel::xSampling,
-             R"pbdoc(
-             int : The x subsampling value
-             )pbdoc")
+             "int : The x subsampling value")
         .def_readwrite("ySampling", &PyChannel::ySampling,
-             R"pbdoc(
-             int : The y subsampling value
-             )pbdoc")
+             "int : The y subsampling value")
         .def_readwrite("pLinear", &PyChannel::pLinear,
-             R"pbdoc(
-             bool : The pLinear value, used for DWA compression.
-             )pbdoc")
+             "bool : The pLinear value, used for DWA compression.")
         .def_readwrite("pixels", &PyChannel::pixels,
-             R"pbdoc(
-             np.array : The channel pixel array.
-             )pbdoc")
+             "np.array : The channel pixel array.")
         .def_readonly("channel_index", &PyChannel::channel_index,
-             R"pbdoc(
-             int : The index of the channel.
-             )pbdoc")
+             "int : The index of the channel.")
         ;
     
-    py::class_<PyPart>(m, "Part", R"pbdoc(
-         The class object representing a part in a EXR image file.
-
-         Example
-         -------  
-         >>> import OpenEXR
-         >>> Z = np.zeros((200,100), dtype='f')
-         >>> P = OpenEXR.Part({}, {"Z" : Z })
-         >>> f = OpenEXR.File([P])
-         >>> f.parts()
-         [Part("Part0", Compression.ZIPS_COMPRESSION, width=100, height=200)] 
-    )pbdoc")
+    py::class_<PyPart>(m, "Part", "The class object representing a part in a EXR image file.\n"
+                                  "\n"
+                                  "Example\n"
+                                  "-------  \n"
+                                  ">>> import OpenEXR\n"
+                                  ">>> Z = np.zeros((200,100), dtype='f')\n"
+                                  ">>> P = OpenEXR.Part({}, {\"Z\" : Z })\n"
+                                  ">>> f = OpenEXR.File([P])\n"
+                                  ">>> f.parts()\n"
+                                  "[Part(\"Part0\", Compression.ZIPS_COMPRESSION, width=100, height=200)] ")
         .def(py::init(),
-             R"pbdoc(
-             Create an empty Part object
-             )pbdoc")
+             "Create an empty Part object")
         .def(py::init<py::dict,py::dict,std::string>(),
              py::arg("header"),
              py::arg("channels"),
              py::arg("name")="",
-             R"pbdoc(
-             Create a Part object from dicts for the header and channels.
-
-             Parameters
-             ----------
-             header : dict
-                 Dict of header metadata, with attribute name as key.
-             channels : list
-                 List of `Channel` objects, which hold pixel numpy arrays.
-             name : str
-                 The name of the part
-
-             Example
-             -------
-             >>> Z = np.zeros((200,100), dtype='f')
-             >>> P = OpenEXR.Part({}, {"Z" : Z }, "left")
-             )pbdoc")
+             "Create a Part object from dicts for the header and channels.\n"
+             "\n"
+             "Parameters\n"
+             "----------\n"
+             "header : dict\n"
+             "    Dict of header metadata, with attribute name as key.\n"
+             "channels : list\n"
+             "    List of `Channel` objects, which hold pixel numpy arrays.\n"
+             "name : str\n"
+             "    The name of the part\n"
+             "\n"
+             "Example\n"
+             "-------\n"
+             ">>> Z = np.zeros((200,100), dtype='f')\n"
+             ">>> P = OpenEXR.Part({}, {\"Z\" : Z }, \"left\")")
         .def("__repr__", [](const PyPart& p) { return repr(p); })
         .def("name", &PyPart::name,
-             R"pbdoc(
-              str : The part name.
-             )pbdoc")
+             "str : The part name.")
         .def("type", &PyPart::type,
-             R"pbdoc(
-              OpenEXR.Storage : The type of the part: scanlineimage, tiledimage, deepscanline, deeptile
-             )pbdoc")
+             "OpenEXR.Storage : The type of the part: scanlineimage, tiledimage, deepscanline, deeptile")
         .def("width", &PyPart::width,
-             R"pbdoc(
-             int : The width of the image, in pixels.
-             )pbdoc")
+             "int : The width of the image, in pixels.")
         .def("height", &PyPart::height,
-             R"pbdoc(
-             int : The height of the image, in pixels.
-             )pbdoc")
+             "int : The height of the image, in pixels.")
         .def("compression", &PyPart::compression,
-             R"pbdoc(
-             OpenEXR.Compression : The compression method:
-                 NO_COMPRESSION
-                 RLE_COMPRESSION
-                 ZIPS_COMPRESSION
-                 ZIP_COMPRESSION
-                 PIZ_COMPRESSION
-                 PXR24_COMPRESSION
-                 B44_COMPRESSION
-                 B44A_COMPRESSION
-                 DWAA_COMPRESSION
-                 DWAB_COMPRESSION
-                 HTJ2K256_COMPRESSION
-                 HTJ2K32_COMPRESSION
-             )pbdoc")
+             "OpenEXR.Compression : The compression method:\n"
+             "    NO_COMPRESSION\n"
+             "    RLE_COMPRESSION\n"
+             "    ZIPS_COMPRESSION\n"
+             "    ZIP_COMPRESSION\n"
+             "    PIZ_COMPRESSION\n"
+             "    PXR24_COMPRESSION\n"
+             "    B44_COMPRESSION\n"
+             "    B44A_COMPRESSION\n"
+             "    DWAA_COMPRESSION\n"
+             "    DWAB_COMPRESSION\n"
+             "    HTJ2K256_COMPRESSION\n"
+             "    HTJ2K32_COMPRESSION")
         .def_readwrite("header", &PyPart::header,
-             R"pbdoc(
-             dict : The header metadata.
-             )pbdoc")
+             "dict : The header metadata.")
         .def_readwrite("channels", &PyPart::channels,
-             R"pbdoc(
-             dict : The channels.
-             )pbdoc")
+             "dict : The channels.")
         .def_readonly("part_index", &PyPart::part_index,
-             R"pbdoc(
-             int : The index of the part.
-             )pbdoc")
+             "int : The index of the part.")
         ;
 
-    py::class_<PyFile>(m, "File", R"pbdoc(
-         The class object representing an EXR image file.
-
-         This class is the interface for reading and writing image
-         header and pixel data.
-
-         Example
-         -------  
-         >>> import OpenEXR
-         >>> f = OpenEXR.File("image.exr")
-         >>> f.header()["comment"] = "Hello, image."
-         >>> f.write("out.exr")
-    )pbdoc")
+    py::class_<PyFile>(m, "File", "The class object representing an EXR image file.\n"
+                                  "\n"
+                                  "This class is the interface for reading and writing image\n"
+                                  "header and pixel data.\n"
+                                  "\n"
+                                  "Example\n"
+                                  "-------  \n"
+                                  ">>> import OpenEXR\n"
+                                  ">>> f = OpenEXR.File(\"image.exr\")\n"
+                                  ">>> f.header()[\"comment\"] = \"Hello, image.\"\n"
+                                  ">>> f.write(\"out.exr\")")
         .def(py::init<>())
         .def(py::init<std::string,bool,bool>(),
              py::arg("filename"),
              py::arg("separate_channels")=false,
              py::arg("header_only")=false,
-             R"pbdoc(
-             Initialize a File by reading the image from the given filename.
-
-             Parameters
-             ----------
-             filename : str
-                 The path to the image file on disk.
-             separate_channels : bool
-                 If True, read each channel into a separate 2D numpy array
-                 if False (default), read pixel data into a single "RGB" or "RGBA" numpy array of dimension (height,width,3) or (height,width,4);
-             header_only : bool
-                 If True, read only the header metadata, not the image pixel data.
-
-             Example
-             -------  
-             >>> f = OpenEXR.File("image.exr", separate_channels=False, header_only=False)
-             )pbdoc")
+             "Initialize a File by reading the image from the given filename.\n"
+             "\n"
+             "Parameters\n"
+             "----------\n"
+             "filename : str\n"
+             "    The path to the image file on disk.\n"
+             "separate_channels : bool\n"
+             "    If True, read each channel into a separate 2D numpy array\n"
+             "    if False (default), read pixel data into a single \"RGB\" or \"RGBA\" numpy array of dimension (height,width,3) or (height,width,4);\n"
+             "header_only : bool\n"
+             "    If True, read only the header metadata, not the image pixel data.\n"
+             "\n"
+             "Example\n"
+             "-------  \n"
+             ">>> f = OpenEXR.File(\"image.exr\", separate_channels=False, header_only=False)")
         .def(py::init<py::dict,py::dict>(),
              py::arg("header"),
              py::arg("channels"),
-             R"pbdoc(
-             Initialize a File with metadata and pixels. Creates a single-part EXR file.
-
-             Parameters
-             ----------
-             header : dict
-                 Dict of header metadata, with attribute name as key.
-             channels : list
-                 List of `Channel` objects, which hold pixel numpy arrays.
-
-             Example
-             -------
-             >>> height, width = (20, 10)
-             >>> R = np.random.rand(height, width).astype('f')
-             >>> G = np.random.rand(height, width).astype('f')
-             >>> B = np.random.rand(height, width).astype('f')
-             >>> channels = { "R" : R, "G" : G, "B" : B }
-             >>> header = { "compression" : OpenEXR.ZIP_COMPRESSION,
-                            "type" : OpenEXR.scanlineimage }
-             >>> f = OpenEXR.File(header, channels)
-        )pbdoc")
+             "Initialize a File with metadata and pixels. Creates a single-part EXR file.\n"
+             "\n"
+             "Parameters\n"
+             "----------\n"
+             "header : dict\n"
+             "    Dict of header metadata, with attribute name as key.\n"
+             "channels : list\n"
+             "    List of `Channel` objects, which hold pixel numpy arrays.\n"
+             "\n"
+             "Example\n"
+             "-------\n"
+             ">>> height, width = (20, 10)\n"
+             ">>> R = np.random.rand(height, width).astype('f')\n"
+             ">>> G = np.random.rand(height, width).astype('f')\n"
+             ">>> B = np.random.rand(height, width).astype('f')\n"
+             ">>> channels = { \"R\" : R, \"G\" : G, \"B\" : B }\n"
+             ">>> header = { \"compression\" : OpenEXR.ZIP_COMPRESSION,\n"
+             "               \"type\" : OpenEXR.scanlineimage }\n"
+             ">>> f = OpenEXR.File(header, channels)")
         .def(py::init<py::list>(),
              py::arg("parts"),
-             R"pbdoc(
-             Initialize a File with a list of Part objects.
-
-             Parameters
-             ----------
-             parts : list
-                 List of Part objects
-
-             Example
-             -------
-             >>> height, width = (20, 10)
-             >>> Z0 = np.zeros((height, width), dtype='f')
-             >>> Z1 = np.ones((height, width), dtype='f')
-             >>> P0 = OpenEXR.Part({}, {"Z" : Z0 })
-             >>> P1 = OpenEXR.Part({}, {"Z" : Z1 })
-             >>> f = OpenEXR.File([P0, P1])
-            )pbdoc")
+             "Initialize a File with a list of Part objects.\n"
+             "\n"
+             "Parameters\n"
+             "----------\n"
+             "parts : list\n"
+             "    List of Part objects\n"
+             "\n"
+             "Example\n"
+             "-------\n"
+             ">>> height, width = (20, 10)\n"
+             ">>> Z0 = np.zeros((height, width), dtype='f')\n"
+             ">>> Z1 = np.ones((height, width), dtype='f')\n"
+             ">>> P0 = OpenEXR.Part({}, {\"Z\" : Z0 })\n"
+             ">>> P1 = OpenEXR.Part({}, {\"Z\" : Z1 })\n"
+             ">>> f = OpenEXR.File([P0, P1])")
         .def("__enter__", &PyFile::__enter__)
         .def("__exit__", &PyFile::__exit__)
         .def_readwrite("filename", &PyFile::filename,
-             R"pbdoc(
-             str : The filename the File was read from.
-
-             Example
-             -------
-             >>> f = OpenEXR.File("image.exr")
-             >>> f.filename
-             'image.exr'
-             )pbdoc")
+             "str : The filename the File was read from.\n"
+             "\n"
+             "Example\n"
+             "-------\n"
+             ">>> f = OpenEXR.File(\"image.exr\")\n"
+             ">>> f.filename\n"
+             "'image.exr'")
         .def_readwrite("parts", &PyFile::parts,
-             R"pbdoc(
-             list : The image parts. The list has a single element for single-part files.
-             Example
-             -------
-             >>> f = OpenEXR.File("image.exr")
-             >>>> f.parts
-             [Part("Part0", Compression.ZIPS_COMPRESSION, width=10, height=20)]         
-             )pbdoc")
+             "list : The image parts. The list has a single element for single-part files.\n"
+             "Example\n"
+             "-------\n"
+             ">>> f = OpenEXR.File(\"image.exr\")\n"
+             ">>>> f.parts\n"
+             "[Part(\"Part0\", Compression.ZIPS_COMPRESSION, width=10, height=20)]         ")
         .def("header", &PyFile::header, py::arg("part_index") = 0,
-             R"pbdoc(
-             dict : The header metadata for the given part if specified, or for the first part if not.
-
-             Parameters
-             ----------
-             part_index : int
-                 The index of the part. Defaults to 0.
-
-             Example
-             -------
-             >>> f = OpenEXR.File("image.exr")
-             >>> f.header()
-             {'dataWindow': (array([0, 0], dtype=int32), array([100, 100], dtype=int32)), 'displayWindow': (array([0, 0], dtype=int32), array([100, 100], dtype=int32))}
-             )pbdoc")
+             "dict : The header metadata for the given part if specified, or for the first part if not.\n"
+             "\n"
+             "Parameters\n"
+             "----------\n"
+             "part_index : int\n"
+             "    The index of the part. Defaults to 0.\n"
+             "\n"
+             "Example\n"
+             "-------\n"
+             ">>> f = OpenEXR.File(\"image.exr\")\n"
+             ">>> f.header()\n"
+             "{'dataWindow': (array([0, 0], dtype=int32), array([100, 100], dtype=int32)), 'displayWindow': (array([0, 0], dtype=int32), array([100, 100], dtype=int32))}")
         .def("channels", &PyFile::channels, py::arg("part_index") = 0,
-             R"pbdoc(
-             Return a dict of channels given part if specified, or for the first part if not. The dict key is the channel name.
-
-             Parameters
-             ----------
-             part_index : int
-                 The index of the part. Defaults to 0.
-
-             Example
-             -------
-             >>> f = OpenEXR.File("image.exr")
-             >>> f.channels(0)
-             {'A': Channel("A", xSampling=1, ySampling=1), 'B': Channel("B", xSampling=1, ySampling=1), 'G': Channel("G", xSampling=1, ySampling=1), 'R': Channel("R", xSampling=1, ySampling=1)}
-             )pbdoc")
+             "Return a dict of channels given part if specified, or for the first part if not. The dict key is the channel name.\n"
+             "\n"
+             "Parameters\n"
+             "----------\n"
+             "part_index : int\n"
+             "    The index of the part. Defaults to 0.\n"
+             "\n"
+             "Example\n"
+             "-------\n"
+             ">>> f = OpenEXR.File(\"image.exr\")\n"
+             ">>> f.channels(0)\n"
+             "{'A': Channel(\"A\", xSampling=1, ySampling=1), 'B': Channel(\"B\", xSampling=1, ySampling=1), 'G': Channel(\"G\", xSampling=1, ySampling=1), 'R': Channel(\"R\", xSampling=1, ySampling=1)}")
         .def("write", &PyFile::write,
-             R"pbdoc(
-             Write the File to the give file name.
-
-             Parameters
-             ----------
-             filename : str
-                 The output path name.
-
-             Example
-             -------
-             >>> f = OpenEXR.File("image.exr")
-             >>> f.write("out.exr"))pbdoc")
+             "Write the File to the give file name.\n"
+             "\n"
+             "Parameters\n"
+             "----------\n"
+             "filename : str\n"
+             "    The output path name.\n"
+             "\n"
+             "Example\n"
+             "-------\n"
+             ">>> f = OpenEXR.File(\"image.exr\")\n"
+             ">>> f.write(\"out.exr\")")
         ;
 }
-
diff -Nru openexr-3.4.6+ds/src/wrappers/python/PyOpenEXR.h openexr-3.4.14/src/wrappers/python/PyOpenEXR.h
--- openexr-3.4.6+ds/src/wrappers/python/PyOpenEXR.h	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/wrappers/python/PyOpenEXR.h	2026-08-05 04:29:55.000000000 +0300
@@ -86,8 +86,12 @@
                                   const std::vector<size_t>& shape, const std::set<std::string>& rgbaChannels,
                                   const Box2i& dw, bool separate_channels);
     int            channelNameToRGBA(const ChannelList& channel_list, const std::string& name,
-                                     std::string& py_channel_name, char& channel_name);
-    
+                                     std::string& py_channel_name, char& channel_name) const;
+
+    void           validateCoalescedChannels(
+        const ChannelList&           channel_list,
+        const std::set<std::string>& rgbaChannels) const;
+
 };
 
 //
diff -Nru openexr-3.4.6+ds/src/wrappers/python/tests/test_deep.py openexr-3.4.14/src/wrappers/python/tests/test_deep.py
--- openexr-3.4.6+ds/src/wrappers/python/tests/test_deep.py	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/wrappers/python/tests/test_deep.py	2026-08-05 04:29:55.000000000 +0300
@@ -224,6 +224,117 @@
 
         os.remove(filename)
 
+    def test_mixed_type_rgb_coalesce_deep_rejected(self):
+        dataWindow = ((0, 0), (0, 0))
+        height = width = 1
+
+        B = np.empty((height, width), dtype=object)
+        G = np.empty((height, width), dtype=object)
+        R = np.empty((height, width), dtype=object)
+        B[0, 0] = np.array([1.0, 2.0], dtype='float16')
+        G[0, 0] = np.array([1.0, 2.0], dtype='float32')
+        R[0, 0] = np.array([1.0, 2.0], dtype='float32')
+
+        channels = {"B": B, "G": G, "R": R}
+        header = {
+            "compression": OpenEXR.ZIPS_COMPRESSION,
+            "type": OpenEXR.deepscanline,
+            "dataWindow": dataWindow,
+        }
+
+        fd, path = tempfile.mkstemp(suffix=".exr")
+        os.close(fd)
+        try:
+            with OpenEXR.File(header, channels) as outfile:
+                outfile.write(path)
+
+            with self.assertRaises(Exception) as ctx:
+                OpenEXR.File(path)
+            self.assertIn("separate_channels", str(ctx.exception))
+
+            with OpenEXR.File(path, separate_channels=True) as infile:
+                self.assertIn("B", infile.channels())
+                self.assertIn("G", infile.channels())
+                self.assertIn("R", infile.channels())
+        finally:
+            if os.path.exists(path):
+                os.remove(path)
+
+    def test_mixed_type_rgb_coalesce_flat_rejected(self):
+        dataWindow = ((0, 0), (0, 0))
+
+        channels = {
+            "B": np.array([[1.0]], dtype='float16'),
+            "G": np.array([[2.0]], dtype='float32'),
+            "R": np.array([[3.0]], dtype='float32'),
+        }
+        header = {
+            "type": OpenEXR.scanlineimage,
+            "dataWindow": dataWindow,
+        }
+
+        fd, path = tempfile.mkstemp(suffix=".exr")
+        os.close(fd)
+        try:
+            with OpenEXR.File(header, channels) as outfile:
+                outfile.write(path)
+
+            with self.assertRaises(Exception) as ctx:
+                OpenEXR.File(path)
+            self.assertIn("separate_channels", str(ctx.exception))
+
+            with OpenEXR.File(path, separate_channels=True) as infile:
+                self.assertIn("B", infile.channels())
+                self.assertIn("G", infile.channels())
+                self.assertIn("R", infile.channels())
+        finally:
+            if os.path.exists(path):
+                os.remove(path)
+
+    def test_literal_rgb_key_collision_deep_rejected(self):
+        dataWindow = ((0, 0), (0, 0))
+        height = width = 1
+
+        left = np.empty((height, width), dtype=object)
+        left_R = np.empty((height, width), dtype=object)
+        left_G = np.empty((height, width), dtype=object)
+        left_B = np.empty((height, width), dtype=object)
+        left[0, 0] = np.array([10.0, 11.0, 12.0, 13.0], dtype='float16')
+        left_R[0, 0] = np.array([10.0, 11.0, 12.0, 13.0], dtype='float16')
+        left_G[0, 0] = np.array([20.0, 21.0, 22.0, 23.0], dtype='float16')
+        left_B[0, 0] = np.array([30.0, 31.0, 32.0, 33.0], dtype='float16')
+
+        channels = {
+            "left": left,
+            "left.R": left_R,
+            "left.G": left_G,
+            "left.B": left_B,
+        }
+        header = {
+            "compression": OpenEXR.ZIPS_COMPRESSION,
+            "type": OpenEXR.deepscanline,
+            "dataWindow": dataWindow,
+        }
+
+        fd, path = tempfile.mkstemp(suffix=".exr")
+        os.close(fd)
+        try:
+            with OpenEXR.File(header, channels) as outfile:
+                outfile.write(path)
+
+            with self.assertRaises(ValueError) as ctx:
+                OpenEXR.File(path)
+            self.assertIn("separate_channels", str(ctx.exception))
+
+            with OpenEXR.File(path, separate_channels=True) as infile:
+                self.assertIn("left", infile.channels())
+                self.assertIn("left.R", infile.channels())
+                self.assertIn("left.G", infile.channels())
+                self.assertIn("left.B", infile.channels())
+        finally:
+            if os.path.exists(path):
+                os.remove(path)
+
 if __name__ == '__main__':
     unittest.main()
     print("OK")
diff -Nru openexr-3.4.6+ds/src/wrappers/python/tests/test_exceptions.py openexr-3.4.14/src/wrappers/python/tests/test_exceptions.py
--- openexr-3.4.6+ds/src/wrappers/python/tests/test_exceptions.py	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/wrappers/python/tests/test_exceptions.py	2026-08-05 04:29:55.000000000 +0300
@@ -9,6 +9,7 @@
 import sys
 import os
 import numpy as np
+import tempfile
 import unittest
 
 import OpenEXR
@@ -153,5 +154,24 @@
         with self.assertRaises(Exception):
             OpenEXR.Channel("C", np.array([0,0,0,0], dtype='uint8').reshape((height, width)), 2, 2)
 
+    def test_dataWindow_mismatch_numpy_pixels(self):
+
+        w, h = 640, 480
+        rgb = np.zeros((w, h, 3), dtype=np.float32)
+
+        header = {
+            "type": OpenEXR.scanlineimage,
+            "compression": OpenEXR.ZIP_COMPRESSION,
+            "displayWindow": ((0, 0), (w - 1, h - 1)),
+            "dataWindow": ((0, 0), (50, 100)),
+        }
+
+        part = OpenEXR.Part(header, {"RGB": rgb})
+        f = OpenEXR.File([part])
+
+        with tempfile.NamedTemporaryFile(suffix=".exr", delete=True) as tmp:
+            with self.assertRaises(ValueError):
+                f.write(tmp.name)
+
 if __name__ == '__main__':
     unittest.main()
diff -Nru openexr-3.4.6+ds/src/wrappers/python/tests/test_rgba.py openexr-3.4.14/src/wrappers/python/tests/test_rgba.py
--- openexr-3.4.6+ds/src/wrappers/python/tests/test_rgba.py	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/wrappers/python/tests/test_rgba.py	2026-08-05 04:29:55.000000000 +0300
@@ -203,6 +203,39 @@
     def test_rgba_f(self):
         self.do_rgba('f')
 
+    def test_literal_rgb_key_collision_flat_rejected(self):
+        dataWindow = ((0, 0), (1, 1))
+
+        channels = {
+            "left": np.array([[10.0, 11.0], [12.0, 13.0]], dtype='float16'),
+            "left.R": np.array([[10.0, 11.0], [12.0, 13.0]], dtype='float16'),
+            "left.G": np.array([[20.0, 21.0], [22.0, 23.0]], dtype='float16'),
+            "left.B": np.array([[30.0, 31.0], [32.0, 33.0]], dtype='float16'),
+        }
+        header = {
+            "type": OpenEXR.scanlineimage,
+            "dataWindow": dataWindow,
+        }
+
+        fd, path = tempfile.mkstemp(suffix=".exr")
+        os.close(fd)
+        try:
+            with OpenEXR.File(header, channels) as outfile:
+                outfile.write(path)
+
+            with self.assertRaises(ValueError) as ctx:
+                OpenEXR.File(path)
+            self.assertIn("separate_channels", str(ctx.exception))
+
+            with OpenEXR.File(path, separate_channels=True) as infile:
+                self.assertIn("left", infile.channels())
+                self.assertIn("left.R", infile.channels())
+                self.assertIn("left.G", infile.channels())
+                self.assertIn("left.B", infile.channels())
+        finally:
+            if os.path.exists(path):
+                os.remove(path)
+
 if __name__ == '__main__':
     unittest.main()
     print("OK")
diff -Nru openexr-3.4.6+ds/src/wrappers/python/tests/test_unittest.py openexr-3.4.14/src/wrappers/python/tests/test_unittest.py
--- openexr-3.4.6+ds/src/wrappers/python/tests/test_unittest.py	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/src/wrappers/python/tests/test_unittest.py	2026-08-05 04:29:55.000000000 +0300
@@ -324,6 +324,8 @@
             # Verify reading it back gives the same data
             with OpenEXR.File(outfilename, separate_channels=True) as infile:
                 compare_files (infile, outfile)
+                if "name" in infile.header():
+                   raise Exception(f"name attribute was added to single part half file")
 
         os.remove(outfilename)
 
@@ -527,6 +529,27 @@
 
         os.remove(outfilename)
 
+    def test_write_float_vector_ndarray(self):
+
+        channels = {
+            "Z" : np.zeros((1, 1), dtype='uint32')
+        }
+        header = {
+            "floatvector32" : np.array([0.0, 1.0, 2.0, 3.0], dtype='float32'),
+            "singlevalue32" : np.array([0.5], dtype='float32')
+        }
+
+        with OpenEXR.File(header, channels) as outfile:
+
+            outfilename = mktemp_outfilename()
+            outfile.write(outfilename)
+
+            with OpenEXR.File(outfilename) as infile:
+                self.assertEqual(infile.header()["floatvector32"], [0.0, 1.0, 2.0, 3.0])
+                self.assertEqual(infile.header()["singlevalue32"], [0.5])
+
+        os.remove(outfilename)
+
     def test_write_float(self):
 
         # Construct a file from scratch and write it.
@@ -585,6 +608,34 @@
 
         os.remove(outfilename)
 
+    def test_unfinished_multipart_load(self):
+
+        #
+        # Multipart files may list more part headers than have valid
+        # pixel data (interrupted write). Loading should return only
+        # parts whose pixels read successfully, not fail the entire
+        # file open.
+        #
+        # The test file unfinished_multipart.exr has 4 parts, 2 valid
+        # and 2 invalid. The default read should return 2 parts; the
+        # header_only read should return all 4 parts.
+        #
+
+        path = f"{test_dir}/unfinished_multipart.exr"
+        num_parts = 4
+        num_good = 2
+
+        with OpenEXR.File(path, separate_channels=True) as f:
+            self.assertEqual(len(f.parts), num_good)
+            for i in range(num_good):
+                self.assertIn('Z', f.parts[i].channels)
+                self.assertEqual(f.parts[i].channels['Z'].pixels.shape, (8, 8))
+            self.assertEqual(f.parts[0].name(), 'Part0')
+            self.assertEqual(f.parts[1].name(), 'Part1')
+
+        with OpenEXR.File(path, separate_channels=True, header_only=True) as f:
+            self.assertEqual(len(f.parts), num_parts)
+
     def test_write_2part(self):
 
         #
@@ -730,5 +781,208 @@
 
         os.remove(outfilename)
 
+    def test_box2i_numpy_scalars(self):
+
+        # Verifying that numpy scalar values are also accepted for
+        # Box2i type
+
+        mn = np.array((0, 0), dtype=np.int32)
+        mx = np.array((15, 15), dtype=np.int32)
+
+        test_cases = [
+            # python int
+            ((0, 0), (15, 15)),  
+            # numpy array
+            (mn, mx),        
+            # numpy scalar values                
+            ((mn[0], mn[1]), (mx[0], mx[1])),    
+        ]
+
+        for data_window in test_cases:
+
+            w = data_window[1][0] - data_window[0][0] + 1
+            h = data_window[1][1] - data_window[0][1] + 1
+
+            header = {
+                "type": OpenEXR.scanlineimage,
+                "dataWindow": data_window,
+            }
+
+            channels = {"RGB": np.zeros((h, w, 3), dtype=np.float16)}
+
+            part = OpenEXR.Part(header, channels)
+            f = OpenEXR.File([part])
+
+            outfilename = mktemp_outfilename()
+            f.write(outfilename)
+
+            # Read back and verify the file structure & attributes
+            with OpenEXR.File(outfilename, header_only=True) as infile:
+                actual_window = infile.parts[0].header["dataWindow"]
+                # Since getAttributeObject returns tuples of numpy arrays,
+                # we need to convert them to standard python tuples before comparison
+                actual_tuple = (
+                    (int(actual_window[0][0]), int(actual_window[0][1])),
+                    (int(actual_window[1][0]), int(actual_window[1][1]))
+                )
+                self.assertEqual(actual_tuple, ((0, 0), (15, 15)))
+
+            if os.path.isfile(outfilename):
+                os.remove(outfilename)
+
+    def test_v2f_numpy_scalars(self):
+        # Verifying that numpy scalar values are also accepted for
+        # V2f (float vector) types. 
+
+        # Setup numpy values for testing
+        center_np_float32 = np.array((0.5, 0.75), dtype=np.float32)
+        
+        test_cases = [
+            # python float 
+            (0.0, 0.0),
+            # numpy array
+            center_np_float32,
+            # numpy float scalars 
+            (center_np_float32[0], center_np_float32[1]),
+            # numpy float64 scalars 
+            (np.float64(0.5), np.float64(0.5)),
+        ]
+
+        # Use a fixed data window for the file structure
+        data_window = ((0, 0), (15, 15))
+        w, h = 16, 16
+
+        for center_value in test_cases:
+            header = {
+                "type": OpenEXR.scanlineimage,
+                "dataWindow": data_window,
+                "displayWindow": data_window,
+                "screenWindowCenter": center_value,  
+            }
+            
+            channels = {"RGB": np.zeros((h, w, 3), dtype=np.float16)}
+
+            part = OpenEXR.Part(header, channels)
+            f = OpenEXR.File([part])
+            
+            outfilename = mktemp_outfilename()
+            f.write(outfilename)
+
+            # Read back and verify the file structure & attributes
+            with OpenEXR.File(outfilename, header_only=True) as inf:
+                header_to_verify = inf.parts[0].header
+
+                # Since getAttributeObject returns tuples of numpy arrays,
+                # we need to convert them to standard python tuples before comparison
+                act_dw = header_to_verify["dataWindow"]
+                dw_tuple = (
+                    (int(act_dw[0][0]), int(act_dw[0][1])),
+                    (int(act_dw[1][0]), int(act_dw[1][1]))
+                )
+                self.assertEqual(dw_tuple, ((0, 0), (15, 15)))
+                
+                act_disp = header_to_verify["displayWindow"]
+                disp_tuple = (
+                    (int(act_disp[0][0]), int(act_disp[0][1])),
+                    (int(act_disp[1][0]), int(act_disp[1][1]))
+                )
+                self.assertEqual(disp_tuple, ((0, 0), (15, 15)))
+                
+                actual_center = header_to_verify["screenWindowCenter"]
+                center_tuple = (float(actual_center[0]), float(actual_center[1]))
+                
+                expected_center = (float(center_value[0]), float(center_value[1]))
+                
+                # Uses assertAlmostEqual on individual items to compare float values 
+                self.assertAlmostEqual(center_tuple[0], expected_center[0], places=5)
+                self.assertAlmostEqual(center_tuple[1], expected_center[1], places=5)
+            
+            if os.path.isfile(outfilename):
+                os.remove(outfilename)
+
+    def test_box2i_float_rejection(self):
+        # dataWindow requires an integer-based bounding box (Box2i).
+        # We test Python floats, NumPy float32, and NumPy float64.
+        invalid_floats = [0.0, np.float32(0.0), np.float64(0.0)]
+        
+        for f in invalid_floats:
+            invalid_float_box = ((f, f), (f + 15.0, f + 15.0))
+            
+            header = {
+                "type": OpenEXR.scanlineimage,
+                "dataWindow": invalid_float_box, 
+            }
+            channels = {"RGB": np.zeros((16, 16, 3), dtype=np.float16)}
+
+            part = OpenEXR.Part(header, channels)
+
+            outfilename = mktemp_outfilename()
+            with self.assertRaises(Exception):
+                with OpenEXR.File([part]) as exr_file:
+                    exr_file.write(outfilename)
+
+            if os.path.isfile(outfilename):
+                os.remove(outfilename)
+
+    def test_v2f_int_rejection(self):
+        # screenWindowCenter requires floating-point coordinates (V2f).
+        # We test Python ints, NumPy int32, and NumPy int64.
+        invalid_ints = [1, np.int32(1), np.int64(1)]
+        
+        data_window = ((0, 0), (15, 15))
+        
+        for i in invalid_ints:
+            invalid_int_center = (i, i + 1) 
+            
+            header = {
+                "type": OpenEXR.scanlineimage,
+                "dataWindow": data_window,
+                "screenWindowCenter": invalid_int_center, 
+            }
+            channels = {"RGB": np.zeros((16, 16, 3), dtype=np.float16)}
+
+            part = OpenEXR.Part(header, channels)
+
+            outfilename = mktemp_outfilename()
+            with self.assertRaises(Exception):
+                with OpenEXR.File([part]) as exr_file:
+                    exr_file.write(outfilename)
+
+            if os.path.isfile(outfilename):
+                os.remove(outfilename)
+    
+    def test_mixed_standard_and_numpy_scalars(self):
+
+        # Allow mixed types seamlessly to match our convenient API design
+        mixed_data_window = ((0, np.int32(0)), (15, np.int64(15)))
+
+        header = {
+            "type": OpenEXR.scanlineimage,
+            "dataWindow": mixed_data_window,
+        }
+
+        channels = {"RGB": np.zeros((16, 16, 3), dtype=np.float16)}
+
+        part = OpenEXR.Part(header, channels)
+        f = OpenEXR.File([part])
+
+        outfilename = mktemp_outfilename()
+
+        f.write(outfilename)
+
+        with OpenEXR.File(outfilename, header_only=True) as inf:
+            act_dw = inf.parts[0].header["dataWindow"]
+            # Since getAttributeObject returns tuples of numpy arrays,
+            # we need to convert them to standard python tuples before comparison
+            dw_tuple = (
+                (int(act_dw[0][0]), int(act_dw[0][1])),
+                (int(act_dw[1][0]), int(act_dw[1][1]))
+            )
+            self.assertEqual(dw_tuple, ((0, 0), (15, 15)))
+
+        if os.path.isfile(outfilename):
+            os.remove(outfilename)
+
+
 if __name__ == '__main__':
     unittest.main()
Binary files /tmp/AjYjgQrW99/openexr-3.4.6+ds/src/wrappers/python/tests/unfinished_multipart.exr and /tmp/7AFeqFljIJ/openexr-3.4.14/src/wrappers/python/tests/unfinished_multipart.exr differ
diff -Nru openexr-3.4.6+ds/website/bin/exrmetrics.rst openexr-3.4.14/website/bin/exrmetrics.rst
--- openexr-3.4.6+ds/website/bin/exrmetrics.rst	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/bin/exrmetrics.rst	2026-08-05 04:29:55.000000000 +0300
@@ -81,6 +81,11 @@
 
    Don't output size data.
 
+.. describe:: --part-disk-size
+
+   Output the on-disk size of the data portion of each part in a multipart EXR file (including chunk headers). 
+   Useful for determining compression impact on part data. File output (``-o``) is not supported when using this option.
+
 .. describe:: --json
 
    Print output as JSON dictionary (default).
diff -Nru openexr-3.4.6+ds/website/index.rst openexr-3.4.14/website/index.rst
--- openexr-3.4.6+ds/website/index.rst	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/index.rst	2026-08-05 04:29:55.000000000 +0300
@@ -19,13 +19,13 @@
 EXR file format, the professional-grade image storage format of the
 motion picture industry.
 
-The purpose of EXR format is to accurately and efficiently represent
-high-dynamic-range scene-linear image data. This is a significant difference to
-most image formats, which store images that are ready for display. Software that
-handles OpenEXR images may need to process them differently to images in other
-formats such as JPEG (see :doc:`SceneLinear` for more details). OpenEXR files
-have strong support for multi-part, multi-channel use cases, and extensive
-representation of associated metadata.
+The purpose of the EXR format is to accurately and efficiently represent
+high-dynamic-range scene-linear image data. This is a significant difference from
+most image formats, which store images that are ready for display. Software may
+need to process OpenEXR images differently from other image formats such as JPEG
+(see :doc:`SceneLinear` for more details). OpenEXR files have strong support
+for multi-part, multi-channel use cases, and extensive representation of
+associated metadata.
 
 OpenEXR is widely used in host application software where accuracy is
 critical, such as photorealistic rendering, texture access, image
@@ -123,6 +123,13 @@
   
   - Submit a Pull Request: https://github.com/AcademySoftwareFoundation/openexr/pulls
 
+  - If you'd like to contribute and could use some ideas of what to
+    do, browse "good first issues" `here
+    <https://github.com/AcademySoftwareFoundation/openexr/contribute),contribute>`_
+    or on `Clotributor <https://clotributor.dev/search?ts_query_web=openexr&page=1>`_.
+
+
+
 Resources
 =========
 
diff -Nru openexr-3.4.6+ds/website/install.rst openexr-3.4.14/website/install.rst
--- openexr-3.4.6+ds/website/install.rst	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/install.rst	2026-08-05 04:29:55.000000000 +0300
@@ -305,6 +305,86 @@
 
 See below for other customization options.
 
+Imath Header Includes (``#include <Imath/...>``)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(new in v3.5)
+
+OpenEXR source and public headers use the conventional Imath include
+form with an ``Imath/`` directory prefix, for example:
+
+.. code-block:: c++
+
+   #include <Imath/ImathVec.h>
+
+**Application code:** If you include OpenEXR headers (or copy patterns
+from them), use the same ``<Imath/...>`` style. That matches how Imath
+installs headers under ``include/Imath/`` and is preferred over flat
+includes such as ``<ImathVec.h>``.
+
+**CMake:** OpenEXR's package configuration calls ``find_dependency(Imath)``
+(see ``cmake/OpenEXRConfig.cmake.in``), and the imported targets
+(``OpenEXR::OpenEXR``, ``OpenEXR::OpenEXRUtil``, etc.) propagate usage
+requirements from Imath. For an Imath install in a non-standard
+location, point CMake at that prefix before configuring OpenEXR or
+your application—for example set ``CMAKE_PREFIX_PATH`` to the Imath
+install root, or set ``Imath_DIR`` to the directory that contains
+``ImathConfig.cmake``. You should not need extra ``target_include_directories``
+for Imath when linking against the OpenEXR imported targets, as long as
+CMake can find Imath the same way OpenEXR did.
+
+**pkg-config:** ``cmake/OpenEXR.pc.in`` lists ``Requires: Imath``. A
+working ``pkg-config --cflags OpenEXR`` invocation therefore merges
+flags from both ``OpenEXR.pc`` and ``Imath.pc``. If either library is
+installed outside default search paths, set ``PKG_CONFIG_PATH`` to
+the ``lib/pkgconfig`` directories that contain those ``.pc`` files.
+
+**Other build systems:** If you are not using CMake or pkg-config, add
+the Imath *parent* include directory to your compiler's include path:
+the directory whose immediate child is the ``Imath`` folder (typically
+``<imath-prefix>/include``), not ``<imath-prefix>/include/Imath`` by
+itself. OpenEXR's own ``-I`` flags from a manual build are analogous:
+you need both OpenEXR's include layout and Imath's, consistent with
+the installed layout.
+
+**Also of note:** When OpenEXR auto-fetches Imath inside its build tree,
+OpenEXR's CMake adds a small compatibility layer so ``<Imath/...>``
+still resolves (see ``cmake/OpenEXRSetup.cmake``); that is internal to
+the OpenEXR build and does not change how a normal installed Imath is
+consumed.
+
+Python Module (Building with Custom Python)
+-------------------------------------------
+
+OpenEXR can optionally build its Python module
+(``OPENEXR_BUILD_PYTHON=ON``), which requires a Python development
+install and the ``pybind11`` package.
+
+If you need to build against a non-default Python installation, you
+must point CMake at a Python **development** environment (not just an
+interpreter). In particular, the build requires ``Python.h`` and a
+linkable Python library.
+
+OpenEXR uses CMake's ``find_package(Python3 ...)`` and
+``find_package(pybind11 ...)``.  To build against a custom Python, use
+the corresponding ``Python3_*`` variables, for example:
+
+.. code-block::
+
+    % cmake -S $srcdir -B $builddir \
+        -DOPENEXR_BUILD_PYTHON=ON \
+        -DPython3_EXECUTABLE=/path/to/python \
+        -DPython3_INCLUDE_DIR=/path/to/python/include \
+        -DPython3_LIBRARY=/path/to/python/lib/libpythonX.Y.so
+
+Notes:
+
+* Embedded Python distributions (for example, some DCC application bundles)
+  may ship a Python interpreter but omit the full development headers and
+  libraries required to compile extension modules.
+* Avoid legacy variables such as ``PYTHON_INCLUDE_DIR`` (all-caps): those are
+  from older CMake modules and may be ignored.
+
 Porting Applications from OpenEXR v2 to v3
 ------------------------------------------
 
diff -Nru openexr-3.4.6+ds/website/latest_news_title.rst openexr-3.4.14/website/latest_news_title.rst
--- openexr-3.4.6+ds/website/latest_news_title.rst	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/latest_news_title.rst	2026-08-05 04:29:55.000000000 +0300
@@ -1,10 +1,4 @@
 ..
   SPDX-License-Identifier: BSD-3-Clause
-<<<<<<< HEAD
-  Copyright Contributors to the OpenEXR Project.
-.. |latest-news-title| replace:: **November  4, 2025 - OpenEXR 3.4.3 Released**
-
-=======
   Copyright (c) Contributors to the OpenEXR Project.
-.. |latest-news-title| replace:: **February 21, 2026 - OpenEXR 3.4.5 Released**
->>>>>>> ff22ba3a (Add news and release notes for v3.4.5 and v3.3.7 (#2252))
+.. |latest-news-title| replace:: **May 24, 2026 - OpenEXR 3.4.12 Released**
diff -Nru openexr-3.4.6+ds/website/news.rst openexr-3.4.14/website/news.rst
--- openexr-3.4.6+ds/website/news.rst	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/news.rst	2026-08-05 04:29:55.000000000 +0300
@@ -17,10 +17,290 @@
 
 
 |latest-news-title|
-===========================================
+======================================
 
 .. _LatestNewsStart:
 
+Patch release that addresses several bugs and security
+vulnerabilities.
+
+* 🐛 Fix several minor memory leaks recovering from reading invalid
+  files.
+* 🐛 The compressor API incorrectly identfied ``HTJ2K`` and ``HTJ2K256`` as
+  lossy; they are lossles.
+* 🐛 Fix CMake AVX feature detection that caused DWA SIMD code to fail on
+  certain architectures.
+* ⚠️ The ``WidenFilename`` utility function is marked as deprecated, to be
+  removed in a future release.
+* ✨ ``exrmetrics`` now print the on-disk size of the data portion of each
+  part. Useful for determining compression impact on part data
+
+For the python module:
+
+* 🐍 🐛 Reject files where the dataWindows does not match the
+  pixel array dimensions.
+* 🐍 ✨ Support NumPy float vector attributes
+* 🐍 ✨ Reading now skips over invalid parts, returns the valid parts only.
+* 🐍 📖 Doc strings have proper indentation
+
+This release addresses the following security vulnerabilities:
+
+* `CVE-2026-45696 <https://www.cve.org/CVERecord?id=CVE-2026-45696>`_
+  OpenEXR ``ht_undo_impl`` heap-buffer-overflow READ via codestream/channel width mismatch in HTJ2K decode
+* `CVE-2026-44663 <https://www.cve.org/CVERecord?id=CVE-2026-44663>`_
+  Integer overflow in HTJ2K decoder ( ``ht_undo_impl`` ) leading to heap-buffer-overflow
+* `OSS-fuzz 512895184 <https://issues.oss-fuzz.com/issues/512895184>`_
+  Null-dereference WRITE in ``Imf_4_0::TileProcess::run_decode``
+* `OSS-fuzz 512314697 <https://issues.oss-fuzz.com/issues/512314697>`_
+  Direct-leak in ``internal_exr_add_part``
+* `OSS-fuzz 508362159 <https://issues.oss-fuzz.com/issues/508362159>`_
+  Heap-buffer-overflow in ``DwaCompressor_uncompress``
+* `OSS-fuzz 507413960 <https://issues.oss-fuzz.com/issues/507413960>`_
+  Heap-buffer-overflow in ``generic_unpack``
+
+.. _LatestNewsEnd:
+
+April 29, 2026 - OpenEXR 3.4.11 Released
+========================================
+
+Patch release that addresses the following security vulnerabilities:
+
+* `CVE-2026-42217 <https://www.cve.org/CVERecord?id=CVE-2026-42217>`_
+Shift exponent overflow in ``readVariableLengthInteger()`` (``ImfIDManifest.cpp``)
+* `CVE-2026-42216 <https://www.cve.org/CVERecord?id=CVE-2026-42216>`_
+Out-of-bounds read in ``IDManifest::init()`` during prefix expansion
+* `CVE-2026-41142 <https://www.cve.org/CVERecord?id=CVE-2026-41142>`_
+Integer overflow in ``ImageChannel::resize`` leads to heap OOB write via OpenEXRUtil public API
+
+* OSS-fuzz `504280155 <https://issues.oss-fuzz.com/issues/504280155>`_
+Heap-buffer-overflow in ``DwaCompressor_uncompress``
+* OSS-fuzz `505062709 <https://issues.oss-fuzz.com/issues/505062709>`_
+Null-dereference READ in ``Imf_3_3::prefixFromLayerName``
+
+Build fixes:
+
+* Fix Windows ARM64EC build issues and correct SIMD ARM NEON path for ARM64/EC
+
+Also, some minor documentation updates:
+
+* GitHub Security Advisories are the preferred way of reporting
+  vulnerabilities, not email.
+* Some clarification around handling of UFT-8 of file paths
+
+
+April 29, 2026 - OpenEXR 3.3.11 Released
+========================================
+
+Patch release for 3.3 that addresses the following security
+vulnerabilities:
+
+* `CVE-2026-42217 <https://www.cve.org/CVERecord?id=CVE-2026-42217>`_
+Shift exponent overflow in ``readVariableLengthInteger()`` (``ImfIDManifest.cpp``)
+* `CVE-2026-42216 <https://www.cve.org/CVERecord?id=CVE-2026-42216>`_
+Out-of-bounds read in ``IDManifest::init()`` during prefix expansion
+* `CVE-2026-41142 <https://www.cve.org/CVERecord?id=CVE-2026-41142>`_
+Integer overflow in ``ImageChannel::resize`` leads to heap OOB write via OpenEXRUtil public API
+
+Also:
+
+* OSS-fuzz `504280155 <https://issues.oss-fuzz.com/issues/504280155>`_
+Heap-buffer-overflow in ``DwaCompressor_uncompress``
+
+April 29, 2026 - OpenEXR 3.2.9 Released
+=======================================
+
+Patch release for 3.2 that addresses the following security
+vulnerabilities:
+
+* `CVE-2026-42217 <https://www.cve.org/CVERecord?id=CVE-2026-42217>`_
+Shift exponent overflow in ``readVariableLengthInteger()`` (``ImfIDManifest.cpp``)
+* `CVE-2026-42216 <https://www.cve.org/CVERecord?id=CVE-2026-42216>`_
+Out-of-bounds read in ``IDManifest::init()`` during prefix expansion
+* `CVE-2026-41142 <https://www.cve.org/CVERecord?id=CVE-2026-41142>`_
+Integer overflow in ``ImageChannel::resize`` leads to heap OOB write via OpenEXRUtil public API
+
+Also:
+
+* OSS-fuzz `504280155 <https://issues.oss-fuzz.com/issues/504280155>`_
+Heap-buffer-overflow in ``DwaCompressor_uncompress``
+
+April 17, 2026 - OpenEXR 3.4.10 Released
+========================================
+
+Patch release that addresses the following security vulnerabilities:
+
+* `CVE-2026-39886 <https://www.cve.org/CVERecord?id=CVE-2026-39886>`_ HTJ2K Signed Integer Overflow in ``ht_undo_impl()``
+* `CVE-2026-40244 <https://www.cve.org/CVERecord?id=CVE-2026-40244>`_ Integer overflow in DWA ``setupChannelData`` ``planarUncRle`` pointer arithmetic (missed variant of CVE-2026-34589)
+* `CVE-2026-40250 <https://www.cve.org/CVERecord?id=CVE-2026-40250>`_ Integer overflow in DWA decoder ``outBufferEnd`` pointer arithmetic (missed variant of CVE-2026-34589)
+
+
+April 17, 2026 - OpenEXR 3.3.10 Released
+========================================
+
+Patch release for v3.3 that addresses the following security vulnerabilities:
+
+* `CVE-2026-40244 <https://www.cve.org/CVERecord?id=CVE-2026-40244>`_ Integer overflow in DWA ``setupChannelData`` ``planarUncRle`` pointer arithmetic (missed variant of CVE-2026-34589)
+* `CVE-2026-40250 <https://www.cve.org/CVERecord?id=CVE-2026-40250>`_ Integer overflow in DWA decoder ``outBufferEnd`` pointer arithmetic (missed variant of CVE-2026-34589)
+
+April 17, 2026 - OpenEXR 3.2.8 Released
+=======================================
+
+Patch release for v3.2 that addresses the following security vulnerabilities:
+
+* `CVE-2026-40244 <https://www.cve.org/CVERecord?id=CVE-2026-40244>`_ Integer overflow in DWA ``setupChannelData`` ``planarUncRle`` pointer arithmetic (missed variant of CVE-2026-34589)
+* `CVE-2026-40250 <https://www.cve.org/CVERecord?id=CVE-2026-40250>`_ Integer overflow in DWA decoder ``outBufferEnd`` pointer arithmetic (missed variant of CVE-2026-34589)
+
+April  3, 2026 - OpenEXR 3.4.9 Released
+=======================================
+
+Patch release that addresses several security vulnerabilities.
+
+This release also fixes a build issue where the library symlinks would
+get installed in the incorrect location when overriding the cached
+install prefix path.
+
+This release addresses the following CVEs:
+
+* `CVE-2026-34589 <https://www.cve.org/CVERecord?id=CVE-2026-34589>`_ DWA Lossy Decoder Heap Out-of-Bounds Write
+* `CVE-2026-34588 <https://www.cve.org/CVERecord?id=CVE-2026-34588>`_ Signed 32-bit Overflow in PIZ Decoder Leads to OOB Read/Write
+* `CVE-2026-34380 <https://www.cve.org/CVERecord?id=CVE-2026-34380>`_ Signed integer overflow (undefined behavior) in undo_pxr24_impl may allow bounds-check bypass in PXR24 decompression
+* `CVE-2026-34379 <https://www.cve.org/CVERecord?id=CVE-2026-34379>`_ Misaligned write in LossyDctDecoder_execute leading to undefined behavior (DWA/DWAB decompression)
+* `CVE-2026-34378 <https://www.cve.org/CVERecord?id=CVE-2026-34378>`_ Signed integer overflow in generic_unpack() when parsing EXR files with crafted negative dataWindow.min.x
+
+
+April  3, 2026 - OpenEXR 3.3.9 Released
+=======================================
+
+Patch release for v3.3 that addresses the following security vulnerabilities:
+
+* `CVE-2026-34589 <https://www.cve.org/CVERecord?id=CVE-2026-34589>`_ DWA Lossy Decoder Heap Out-of-Bounds Write
+* `CVE-2026-34588 <https://www.cve.org/CVERecord?id=CVE-2026-34588>`_ Signed 32-bit Overflow in PIZ Decoder Leads to OOB Read/Write
+* `CVE-2026-34544 <https://www.cve.org/CVERecord?id=CVE-2026-34544>`_ integer overflow to OOB write in uncompress_b44_impl()
+* `CVE-2026-34543 <https://www.cve.org/CVERecord?id=CVE-2026-34543>`_ Heap information disclosure in PXR24 decompression via unchecked decompressed size (undo_pxr24_impl)
+* `CVE-2026-34380 <https://www.cve.org/CVERecord?id=CVE-2026-34380>`_ Signed integer overflow (undefined behavior) in undo_pxr24_impl may allow bounds-check bypass in PXR24 decompression
+* `CVE-2026-34379 <https://www.cve.org/CVERecord?id=CVE-2026-34379>`_ Misaligned write in LossyDctDecoder_execute leading to undefined behavior (DWA/DWAB decompression)
+* `CVE-2026-34378 <https://www.cve.org/CVERecord?id=CVE-2026-34378>`_ Signed integer overflow in generic_unpack() when parsing EXR files with crafted negative dataWindow.min.x
+
+
+April  3, 2026 - OpenEXR 3.2.7 Released
+=======================================
+
+Patch release for v3.2 that addresses the following security vulnerabilities:
+
+* `CVE-2026-34589 <https://www.cve.org/CVERecord?id=CVE-2026-34589>`_ DWA Lossy Decoder Heap Out-of-Bounds Write
+* `CVE-2026-34588 <https://www.cve.org/CVERecord?id=CVE-2026-34588>`_ Signed 32-bit Overflow in PIZ Decoder Leads to OOB Read/Write
+* `CVE-2026-34544 <https://www.cve.org/CVERecord?id=CVE-2026-34544>`_ integer overflow to OOB write in uncompress_b44_impl()
+* `CVE-2026-34543 <https://www.cve.org/CVERecord?id=CVE-2026-34543>`_ Heap information disclosure in PXR24 decompression via unchecked decompressed size (undo_pxr24_impl)
+* `CVE-2026-34380 <https://www.cve.org/CVERecord?id=CVE-2026-34380>`_ Signed integer overflow (undefined behavior) in undo_pxr24_impl may allow bounds-check bypass in PXR24 decompression
+* `CVE-2026-34379 <https://www.cve.org/CVERecord?id=CVE-2026-34379>`_ Misaligned write in LossyDctDecoder_execute leading to undefined behavior (DWA/DWAB decompression)
+
+
+March 26, 2026 - OpenEXR 3.4.8 Released
+=======================================
+
+Patch release with several bug/build fixes:
+
+* Fix an integer-overflow bug reading malformed files compressed with
+  B44A/B44B 
+* Fix a buffer-overrun bug reading malformed files compressed with PXR24
+* Fix a bug compressing half data with ZIPS/ZIP data when the
+  compressed size equals packed size
+* Single part files no longer get assigned a part name when writing
+  via the python module
+* Fix a build failure on FreeBSD involving ``threads.h``
+
+This also eliminates several compiler warnings, particularly about the
+deprecated ``isOptimizationEnabled()`` API and deprecates standard
+attributes.
+
+
+March  15, 2026 - OpenEXR 3.4.7 Released
+========================================
+
+Patch release bug/build fixes:
+
+* Fix an integer overflow decoding very wide htj2k images
+* Fix build failure with glibc 2.43
+* Fix Windows symbol visibility warnings
+
+Full changelog: ``v3.4.6..v3.4.7 <https://github.com/AcademySoftwareFoundation/openexr/compare/v3.4.6..v3.4.7>``_
+
+March  1, 2026 - OpenEXR 3.4.6 Released
+=======================================
+
+Patch release with several bug fixes, enhancements, and build improvements.
+
+* 🐛 A limit of ``UINT_MAX`` deep samples per pixel is now
+  enforced, which prevents an integer overflow when using the
+  ``CompositeDeepScanLine`` API to combine multiple deep parts.
+
+* 🐛 ``IlmThread`` now builds properl with glibc 2.43.
+
+* 🔧 In ``IlmThreadPool``, replace deprecated ``std::atomic_load /
+  std::atomic_exchange`` overloads for ``std::shared_ptr`` with the C++20
+  ``std::atomic<std::shared_ptr<T>>`` interface when available.
+
+* 🐛 The ``ZIP`` and ``ZIPS`` Compressor objects had incorrect
+  compression types set, although the ill effects were miminal as the
+  value is seldom used.
+  
+* 🐛 Enable SSE2 on 32-bit x86 builds to fix test failures
+
+Build improvements:
+
+* 🛠️ OpenEXR now ships with an internal "vendored" copy of the
+  ``OpenJPH`` library. At configuration time, if CMake finds an
+  external installation of ``OpenJPH``, it will use it, but if that
+  fails, or the CMake configuration option
+  ``OPENEXR_FORCE_INTERNAL_OPENJPH`` is set, it will use the internal
+  copy. For OpenEXR v3.4.6, the vendored version of OpenJPH is 0.26.3.
+
+* 🛠️ A new CMake build option ``DOPENEXR_FORCE_EMBEDDED_CORE``
+  builds the ``OpenEXRCore`` library statically and links it into the
+  ``OpenEXR`` library with symbols hidden, making it possible to link
+  multiple versions of OpenEXR into the same executable. The ``OpenEXR``
+  library has namespaces for this situation, but the ``OpenEXRCore``
+  library is written in C with no configurable namespace. See `Linking
+  Multiple OpenEXR Versions in the
+  Same Executable <https://openexr.com/en/latest/install.html#linking-multiple-openexr-versions-in-the-executable>`_
+  for details.
+
+* 🛠️ Fix build failure with ``-march=sandybridge``
+
+Python:
+
+* 🐍 ✨ Add python support for Opaque Attributes
+
+* 🚀 Python module I/O operations now release the GIL,
+  preventing blocking.
+
+Tools:
+
+* ✨ Add sanity check for correct arguments to exrstdattr, to
+  catch mistakes such as ``exrstdattr input.exr output.exr -comment
+  hello`` which would formerly write to a file called ``hello`` but
+  now fails with a usage error.
+
+Also, this release bumps the vendered version of ``libdeflate`` to 1.25.
+
+
+March 1, 2026 - OpenEXR 3.3.8 Released
+======================================
+
+Patch release for the 3.3 release that prevents an integer overflow
+when using the ``CompositeDeepScanLine`` API to combine multiple deep
+parts.
+
+March 1, 2026 - OpenEXR 3.2.6 Released
+======================================
+
+Patch release for the 3.2 release that prevents an integer overflow
+when using the ``CompositeDeepScanLine`` API to combine multiple deep
+parts.
+
+February 21, 2026 - OpenEXR 3.4.5 Released
+==========================================
+
 Patch release that fixes an incorrect size check in
 `istream_nonparallel_read` that could lead to a buffer overflow on
 invalid input data.
diff -Nru openexr-3.4.6+ds/website/ReadingAndWritingImageFiles.rst openexr-3.4.14/website/ReadingAndWritingImageFiles.rst
--- openexr-3.4.6+ds/website/ReadingAndWritingImageFiles.rst	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/ReadingAndWritingImageFiles.rst	2026-08-05 04:29:55.000000000 +0300
@@ -72,6 +72,19 @@
 When a multi-resolution file is read via a scan line interface, only the
 highest-resolution version of the image is accessible.
 
+File Paths and UTF-8
+--------------------
+
+File path arguments passed as ``const char*`` to the OpenEXR C++ API
+(when constructing ``InputFile``, ``OutputFile``, ``RgbaInputFile``,
+or when calling ``isOpenExrFile``) must use **UTF-8** encoding.  This
+is true on all platforms, including Microsoft Windows.  Under the
+hood, the library opens files named as ``const char*`` through
+``std::filesystem::u8path``.
+
+Note that the OpenEXRCore C layer documents the same assumption of
+**UTF-8** encoding.
+
 Multi-Part and Deep Data
 ------------------------
 
@@ -1503,6 +1516,46 @@
 Miscellaneous
 =============
 
+Image Size Limits and Out-of-Memory Failures
+--------------------------------------------
+
+The OpenEXR file format places no fixed limit on image size, except
+that image width and height are represented by signed 32-bit integers
+and therefore technically limited to a maximum of 2,147,483,647.
+
+Attempting to read a very large image may result in an "out-of-memory
+failure. This is not considered a security vulnerability. The memory
+required to decode such an image is inherently proportional to its
+pixel count, even if compression reduces the image to a small file
+size on disk. Exhausting available memory on a given machine is a
+system resource constraint, not a library defect — the same file that
+triggers an out-of-memory error on one machine may load successfully
+on another with more memory.
+
+The OpenEXR library provides 
+``Imf::Header::setMaxImageSize(int maxWidth,int maxHeight)`` and
+``Imf::Header:"setMaxTileSize(int maxWidth,int maxHeight)`` (and
+``exr_set_default_maximum_image_size()`` and
+``exr_set_default_maximum_tile_size()`` in OpenEXRCore) to allow
+applications to reject files with dimensions exceeding a configurable
+limit before any large allocation occurs. Applications processing
+untrusted EXR files should set these limits to values appropriate for
+their deployment environment.
+
+Beware of setting this limit too low.  Images of resolution greater
+than 10k are not uncommon in VFX workflows. Unless your application
+must process untrusted EXR files in an environment where failure is
+catastrophic, it may be best to provide a limit but allow it to be
+configured by the user:
+
+.. literalinclude:: src/exrreader_max/exrreader_max.cpp
+   :lines: 5-
+
+For very large images, the tiled OpenEXR format is more efficient, and
+reading via the tiled API is the recommended method.  Application code
+that uses the scanline API inherently require significant amounts of
+memory when opening very large tiled images.
+
 Is this an OpenEXR File?
 ------------------------
 
diff -Nru openexr-3.4.6+ds/website/requirements-lock.txt openexr-3.4.14/website/requirements-lock.txt
--- openexr-3.4.6+ds/website/requirements-lock.txt	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/website/requirements-lock.txt	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,323 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+#    pip-compile --generate-hashes --output-file=website/requirements-lock.txt website/requirements.txt
+#
+alabaster==0.7.16 \
+    --hash=sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65 \
+    --hash=sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92
+    # via sphinx
+babel==2.18.0 \
+    --hash=sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d \
+    --hash=sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35
+    # via sphinx
+breathe==4.36.0 \
+    --hash=sha256:14860b73118ac140b7a3f55446890c777d1b67149cb024279fe3710dad7f535c \
+    --hash=sha256:af85436f1f09e842bd1fd95617281211c635f8768d245ff830c59b979888d1d5
+    # via -r website/requirements.txt
+certifi==2026.2.25 \
+    --hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
+    --hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
+    # via requests
+charset-normalizer==3.4.6 \
+    --hash=sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e \
+    --hash=sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c \
+    --hash=sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5 \
+    --hash=sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815 \
+    --hash=sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f \
+    --hash=sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0 \
+    --hash=sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484 \
+    --hash=sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407 \
+    --hash=sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6 \
+    --hash=sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8 \
+    --hash=sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264 \
+    --hash=sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815 \
+    --hash=sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2 \
+    --hash=sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4 \
+    --hash=sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579 \
+    --hash=sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f \
+    --hash=sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa \
+    --hash=sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95 \
+    --hash=sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab \
+    --hash=sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297 \
+    --hash=sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a \
+    --hash=sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e \
+    --hash=sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84 \
+    --hash=sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8 \
+    --hash=sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0 \
+    --hash=sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9 \
+    --hash=sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f \
+    --hash=sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1 \
+    --hash=sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843 \
+    --hash=sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565 \
+    --hash=sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7 \
+    --hash=sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c \
+    --hash=sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b \
+    --hash=sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7 \
+    --hash=sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687 \
+    --hash=sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9 \
+    --hash=sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14 \
+    --hash=sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89 \
+    --hash=sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f \
+    --hash=sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0 \
+    --hash=sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9 \
+    --hash=sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a \
+    --hash=sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389 \
+    --hash=sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0 \
+    --hash=sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30 \
+    --hash=sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd \
+    --hash=sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e \
+    --hash=sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9 \
+    --hash=sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc \
+    --hash=sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532 \
+    --hash=sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d \
+    --hash=sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae \
+    --hash=sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2 \
+    --hash=sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64 \
+    --hash=sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f \
+    --hash=sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557 \
+    --hash=sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e \
+    --hash=sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff \
+    --hash=sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398 \
+    --hash=sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db \
+    --hash=sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a \
+    --hash=sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43 \
+    --hash=sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597 \
+    --hash=sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c \
+    --hash=sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e \
+    --hash=sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2 \
+    --hash=sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54 \
+    --hash=sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e \
+    --hash=sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4 \
+    --hash=sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4 \
+    --hash=sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7 \
+    --hash=sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6 \
+    --hash=sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5 \
+    --hash=sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194 \
+    --hash=sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69 \
+    --hash=sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f \
+    --hash=sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316 \
+    --hash=sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e \
+    --hash=sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73 \
+    --hash=sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8 \
+    --hash=sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923 \
+    --hash=sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88 \
+    --hash=sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f \
+    --hash=sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21 \
+    --hash=sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4 \
+    --hash=sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6 \
+    --hash=sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc \
+    --hash=sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2 \
+    --hash=sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866 \
+    --hash=sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021 \
+    --hash=sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2 \
+    --hash=sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d \
+    --hash=sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8 \
+    --hash=sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de \
+    --hash=sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237 \
+    --hash=sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4 \
+    --hash=sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778 \
+    --hash=sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb \
+    --hash=sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc \
+    --hash=sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602 \
+    --hash=sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4 \
+    --hash=sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f \
+    --hash=sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5 \
+    --hash=sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611 \
+    --hash=sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8 \
+    --hash=sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf \
+    --hash=sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d \
+    --hash=sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b \
+    --hash=sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db \
+    --hash=sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e \
+    --hash=sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077 \
+    --hash=sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd \
+    --hash=sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef \
+    --hash=sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e \
+    --hash=sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8 \
+    --hash=sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe \
+    --hash=sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058 \
+    --hash=sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17 \
+    --hash=sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833 \
+    --hash=sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421 \
+    --hash=sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550 \
+    --hash=sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff \
+    --hash=sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2 \
+    --hash=sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc \
+    --hash=sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982 \
+    --hash=sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d \
+    --hash=sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed \
+    --hash=sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104 \
+    --hash=sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659
+    # via requests
+colorama==0.4.6 \
+    --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+    --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+    # via -r website/requirements.txt
+docutils==0.20.1 \
+    --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \
+    --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b
+    # via sphinx
+idna==3.15 \
+    --hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \
+    --hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc
+    # via
+    #   -r website/requirements.txt
+    #   requests
+imagesize==2.0.0 \
+    --hash=sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96 \
+    --hash=sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3
+    # via sphinx
+jinja2==3.1.6 \
+    --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
+    --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
+    # via sphinx
+markupsafe==3.0.3 \
+    --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
+    --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
+    --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
+    --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
+    --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
+    --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \
+    --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
+    --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
+    --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
+    --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
+    --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
+    --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \
+    --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \
+    --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
+    --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
+    --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
+    --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \
+    --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \
+    --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
+    --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
+    --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
+    --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \
+    --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \
+    --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
+    --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
+    --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \
+    --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
+    --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \
+    --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
+    --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \
+    --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
+    --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \
+    --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
+    --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
+    --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
+    --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
+    --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \
+    --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
+    --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
+    --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \
+    --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
+    --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
+    --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
+    --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
+    --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \
+    --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \
+    --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
+    --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
+    --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
+    --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
+    --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
+    --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
+    --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \
+    --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
+    --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
+    --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
+    --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \
+    --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
+    --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
+    --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
+    --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \
+    --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
+    --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
+    --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
+    --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \
+    --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
+    --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \
+    --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
+    --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \
+    --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
+    --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
+    --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
+    --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \
+    --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \
+    --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \
+    --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
+    --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \
+    --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
+    --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
+    --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \
+    --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
+    --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
+    --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
+    --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
+    --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
+    --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \
+    --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \
+    --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
+    --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
+    # via jinja2
+packaging==26.0 \
+    --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
+    --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
+    # via sphinx
+pygments==2.19.2 \
+    --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
+    --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
+    # via sphinx
+requests==2.32.5 \
+    --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \
+    --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf
+    # via sphinx
+snowballstemmer==3.0.1 \
+    --hash=sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064 \
+    --hash=sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895
+    # via sphinx
+sphinx==7.4.7 \
+    --hash=sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe \
+    --hash=sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239
+    # via
+    #   -r website/requirements.txt
+    #   breathe
+    #   sphinx-press-theme
+sphinx-press-theme==0.9.1 \
+    --hash=sha256:1643dee7365f7831d1d3971b389b7c255641a7aced75f0681f71574e380046cf \
+    --hash=sha256:eed3fdd8df249b67136b507dfc6a84d1a2c5feca5376960c6d4d28ada4f6cdf7
+    # via -r website/requirements.txt
+sphinxcontrib-applehelp==2.0.0 \
+    --hash=sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1 \
+    --hash=sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5
+    # via sphinx
+sphinxcontrib-devhelp==2.0.0 \
+    --hash=sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad \
+    --hash=sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2
+    # via sphinx
+sphinxcontrib-htmlhelp==2.1.0 \
+    --hash=sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8 \
+    --hash=sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9
+    # via sphinx
+sphinxcontrib-jsmath==1.0.1 \
+    --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \
+    --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8
+    # via sphinx
+sphinxcontrib-qthelp==2.0.0 \
+    --hash=sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab \
+    --hash=sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb
+    # via sphinx
+sphinxcontrib-serializinghtml==2.0.0 \
+    --hash=sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 \
+    --hash=sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d
+    # via sphinx
+urllib3==2.7.0 \
+    --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
+    --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
+    # via
+    #   -r website/requirements.txt
+    #   requests
diff -Nru openexr-3.4.6+ds/website/requirements.txt openexr-3.4.14/website/requirements.txt
--- openexr-3.4.6+ds/website/requirements.txt	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/requirements.txt	2026-08-05 04:29:55.000000000 +0300
@@ -1,5 +1,12 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright (c) Contributors to the OpenEXR Project.
-sphinx == 7.1.2
-breathe
+#
+# To regenerate requirements-lock.txt (hashed deps for Scorecard Pinned-Dependencies):
+#   pip-compile requirements.txt --generate-hashes -o requirements-lock.txt
+#
+colorama==0.4.6 # necessary on Windows only
+sphinx==7.4.7
+breathe==4.36.0
 sphinx-press-theme
+urllib3>=2.7 # transitive via requests; floor for security fixes
+idna>=3.18  # transitive via requests; floor for CVE-2026-45409 / GHSA-65pc-fj4g-8rjx
diff -Nru openexr-3.4.6+ds/website/src/exrreader/CMakeLists.txt openexr-3.4.14/website/src/exrreader/CMakeLists.txt
--- openexr-3.4.6+ds/website/src/exrreader/CMakeLists.txt	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/src/exrreader/CMakeLists.txt	2026-08-05 04:29:55.000000000 +0300
@@ -5,3 +5,4 @@
 add_executable(${PROJECT_NAME} exrreader.cpp)
 target_link_libraries(${PROJECT_NAME} OpenEXR::OpenEXR)
 
+
diff -Nru openexr-3.4.6+ds/website/src/exrreader_max/CMakeLists.txt openexr-3.4.14/website/src/exrreader_max/CMakeLists.txt
--- openexr-3.4.6+ds/website/src/exrreader_max/CMakeLists.txt	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/website/src/exrreader_max/CMakeLists.txt	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,12 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) Contributors to the OpenEXR Project.
+#
+
+cmake_minimum_required(VERSION 3.12)
+project(exrreader_max)
+find_package(OpenEXR REQUIRED)
+
+add_executable(${PROJECT_NAME} exrreader_max.cpp)
+target_link_libraries(${PROJECT_NAME} OpenEXR::OpenEXR)
+
diff -Nru openexr-3.4.6+ds/website/src/exrreader_max/exrreader_max.cpp openexr-3.4.14/website/src/exrreader_max/exrreader_max.cpp
--- openexr-3.4.6+ds/website/src/exrreader_max/exrreader_max.cpp	1970-01-01 02:00:00.000000000 +0200
+++ openexr-3.4.14/website/src/exrreader_max/exrreader_max.cpp	2026-08-05 04:29:55.000000000 +0300
@@ -0,0 +1,100 @@
+//
+// SPDX-License-Identifier: BSD-3-Clause
+// Copyright (c) Contributors to the OpenEXR Project.
+//
+
+#include <ImfRgbaFile.h>
+#include <ImfArray.h>
+#include <cstring>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+
+static void
+usage(const char *prog)
+{
+    std::cerr << "usage: " << prog
+              << " [--maxImageSize <width> <height>] <image.exr>\n";
+}
+
+int
+main(int argc, char *argv[])
+{
+    int         maxW = 10000;
+    int         maxH = 10000;
+    std::string path;
+
+    for (int i = 1; i < argc; ++i)
+    {
+        if (std::strcmp(argv[i], "--maxImageSize") == 0)
+        {
+            if (i + 2 >= argc)
+            {
+                usage(argv[0]);
+                return 1;
+            }
+            try
+            {
+                maxW = std::stoi(argv[i + 1]);
+                maxH = std::stoi(argv[i + 2]);
+            }
+            catch (const std::exception &)
+            {
+                std::cerr << "invalid width or height for --maxImageSize\n";
+                return 1;
+            }
+            if (maxW <= 0 || maxH <= 0)
+            {
+                std::cerr << "--maxImageSize width and height must be positive\n";
+                return 1;
+            }
+            i += 2;
+        }
+        else if (argv[i][0] == '-')
+        {
+            std::cerr << "unknown option: " << argv[i] << std::endl;
+            usage(argv[0]);
+            return 1;
+        }
+        else
+        {
+            if (!path.empty())
+            {
+                std::cerr << "unexpected extra argument: " << argv[i]
+                          << std::endl;
+                usage(argv[0]);
+                return 1;
+            }
+            path = argv[i];
+        }
+    }
+
+    if (path.empty())
+    {
+        usage(argv[0]);
+        return 1;
+    }
+
+    Imf::Header::setMaxImageSize(maxW, maxH);
+
+    try
+    {
+        Imf::RgbaInputFile file(path.c_str());
+        Imath::Box2i       dw = file.dataWindow();
+        int                width  = dw.max.x - dw.min.x + 1;
+        int                height = dw.max.y - dw.min.y + 1;
+
+        Imf::Array2D<Imf::Rgba> pixels(width, height);
+
+        file.setFrameBuffer(&pixels[0][0], 1, width);
+        file.readPixels(dw.min.y, dw.max.y);
+    }
+    catch (const std::exception &e)
+    {
+        std::cerr << "error reading image file " << path << ": " << e.what()
+                  << std::endl;
+        return 1;
+    }
+
+    return 0;
+}
diff -Nru openexr-3.4.6+ds/website/TechnicalIntroduction.rst openexr-3.4.14/website/TechnicalIntroduction.rst
--- openexr-3.4.6+ds/website/TechnicalIntroduction.rst	2026-03-01 23:08:42.000000000 +0200
+++ openexr-3.4.14/website/TechnicalIntroduction.rst	2026-08-05 04:29:55.000000000 +0300
@@ -139,6 +139,32 @@
   regular images. With multi-part files, different views are stored in
   different parts.
 
+Image Size Limits and Out-of-Memory Failures
+--------------------------------------------
+
+The OpenEXR file format places no fixed limit on image size, except
+that image width and height are represented by signed 32-bit integers
+and therefore technically limited to a maximum of 2,147,483,647.
+
+Attempting to read a very large image may result in an "out-of-memory
+failure. This is not considered a security vulnerability. The memory
+required to decode such an image is inherently proportional to its
+pixel count, even if compression reduces the image to a small file
+size on disk. Exhausting available memory on a given machine is a
+system resource constraint, not a library defect — the same file that
+triggers an out-of-memory error on one machine may load successfully
+on another with more memory.
+
+The OpenEXR library provides 
+``Imf::Header::setMaxImageSize(int maxWidth,int maxHeight)`` and
+``Imf::Header:"setMaxTileSize(int maxWidth,int maxHeight)`` (and
+``exr_set_default_maximum_image_size()`` and
+``exr_set_default_maximum_tile_size()`` in OpenEXRCore) to allow
+applications to reject files with dimensions exceeding a configurable
+limit before any large allocation occurs. Applications processing
+untrusted EXR files should set these limits to values appropriate for
+their deployment environment.
+
 Overview of the OpenEXR File Format
 ===================================
 
@@ -839,6 +865,17 @@
        efficient space wise and faster to decode full frames than DWAA
        access.
 
+   * - HTJ2K256 (lossless)
+ 
+     - Lossless compression of HALF, FLOAT and UINT data types in blocks of 256 scanlines, 
+       using `JPEG 2000 Part 15 (High-throughput JPEG 2000) <https://www.itu.int/rec/T-REC-T.814>`_, 
+
+   * - HTJ2K32 (lossless)
+ 
+     - Lossless compression of HALF, FLOAT and UINT data types in blocks of 32 scanlines, 
+       using `JPEG 2000 Part 15 (High-throughput JPEG 2000) <https://www.itu.int/rec/T-REC-T.814>`_, 
+       
+
 Luminance/Chroma Images
 =======================
 
