diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst
index bbb954e25..991699727 100644
--- a/docs/releasenotes/8.0.0.rst
+++ b/docs/releasenotes/8.0.0.rst
@@ -50,6 +50,17 @@ Add MIME type to PsdImagePlugin
 API Additions
 =============
 
+Image.open: add formats parameter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Added a new ``formats`` parameter to :py:func:`.Image.open`:
+
+* A list or tuple of formats to attempt to load the file in.
+  This can be used to restrict the set of formats checked.
+  Pass ``None`` to try all supported formats. You can print the set of
+  available formats by running ``python -m PIL`` or using
+  the :py:func:`PIL.features.pilinfo` function.
+
 ImageOps.autocontrast: add mask parameter
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 30ee8606a..a56494104 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -2837,7 +2837,7 @@ def _decompression_bomb_check(size):
         )
 
 
-def open(fp, mode="r"):
+def open(fp, mode="r", formats=None):
     """
     Opens and identifies the given image file.
 
@@ -2852,12 +2852,18 @@ def open(fp, mode="r"):
        ``file.seek``, and ``file.tell`` methods,
        and be opened in binary mode.
     :param mode: The mode.  If given, this argument must be "r".
+    :param formats: A list or tuple of formats to attempt to load the file in.
+       This can be used to restrict the set of formats checked.
+       Pass ``None`` to try all supported formats. You can print the set of
+       available formats by running ``python -m PIL`` or using
+       the :py:func:`PIL.features.pilinfo` function.
     :returns: An :py:class:`~PIL.Image.Image` object.
     :exception FileNotFoundError: If the file cannot be found.
     :exception PIL.UnidentifiedImageError: If the image cannot be opened and
        identified.
     :exception ValueError: If the ``mode`` is not "r", or if a ``StringIO``
        instance is used for ``fp``.
+    :exception TypeError: If ``formats`` is not ``None``, a list or a tuple.
     """
 
     if mode != "r":
@@ -2868,6 +2874,11 @@ def open(fp, mode="r"):
             "Binary data must be used instead."
         )
 
+    if formats is None:
+        formats = ID
+    elif not isinstance(formats, (list, tuple)):
+        raise TypeError("formats must be a list or tuple")
+
     exclusive_fp = False
     filename = ""
     if isinstance(fp, Path):
@@ -2891,8 +2902,8 @@ def open(fp, mode="r"):
 
     accept_warnings = []
 
-    def _open_core(fp, filename, prefix):
-        for i in ID:
+    def _open_core(fp, filename, prefix, formats):
+        for i in formats:
             try:
                 factory, accept = OPEN[i]
                 result = not accept or accept(prefix)
@@ -2914,11 +2925,11 @@ def open(fp, mode="r"):
                 raise
         return None
 
-    im = _open_core(fp, filename, prefix)
+    im = _open_core(fp, filename, prefix, formats)
 
     if im is None:
         if init():
-            im = _open_core(fp, filename, prefix)
+            im = _open_core(fp, filename, prefix, formats)
 
     if im:
         im._exclusive_fp = exclusive_fp

Load a restricted subset of image formats
Image parsing libraries are, and always have been, complicated pieces of software, with a requirement to (a) handle complicated input formats with a vast number of edge cases, and (b) operate as quickly and efficiently as possible.

This is a combination that leads to a lot of security bugs.

Pillow is no exception to this rule.

In many cases, when building an application, it makes sense to reduce the possible set of security bugs by only handling the input formats that you actually need to handle.

For example, if I only need to handle JPEG files, there is no reason for me to want to run a PostScript interpreter.  If I can design my program so it is *impossible* for the PostScript interpreter to be invoked, no matter what garbage input my program receives, then there's a huge category of security issues I don't need to worry about.

So, if I know what format the input file uses, is there a supported way to load it using *only* the required plugin?

There doesn't appear to be any documented way of doing this with Pillow.  As far as I can see, the only documented way of reading an image file is to use PIL.Image.open.  And if the input is malformed, then PIL.Image.open will potentially invoke *any* of the available format plugins, and will do so in an unpredictable order (depending on whether PIL.Image.init() or PIL.Image.preinit() is invoked first.)

An *undocumented* way would be to do this:
```python
PIL.Image.init()
image = PIL.Image.OPEN['JPEG'][0](fp, filename)
```
But this is pretty cryptic and I wouldn't assume this will always work with future Pillow versions.  This also skips the _decompression_bomb_check that is performed by PIL.Image.open.

diff --git a/Tests/test_image.py b/Tests/test_image.py
index 6d188e740..9e664a7c8 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -87,6 +87,26 @@ class TestImage:
         # with pytest.raises(MemoryError):
         #   Image.new("L", (1000000, 1000000))
 
+    def test_open_formats(self):
+        PNGFILE = "Tests/images/hopper.png"
+        JPGFILE = "Tests/images/hopper.jpg"
+
+        with pytest.raises(TypeError):
+            Image.open(PNGFILE, formats=123)
+
+        for formats in [["JPEG"], ("JPEG",)]:
+            with pytest.raises(UnidentifiedImageError):
+                Image.open(PNGFILE, formats=formats)
+
+            with Image.open(JPGFILE, formats=formats) as im:
+                assert im.mode == "RGB"
+                assert im.size == (128, 128)
+
+        for file in [PNGFILE, JPGFILE]:
+            with Image.open(file, formats=None) as im:
+                assert im.mode == "RGB"
+                assert im.size == (128, 128)
+
     def test_width_height(self):
         im = Image.new("RGB", (1, 2))
         assert im.width == 1

pip install -e .[dev]
parse_log_pytest
requirements.txt
apt-get update apt-get install -y gcc libjpeg-dev zlib1g-dev libtiff5-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev libharfbuzz-dev libfribidi-dev libxcb1-dev
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
babel==2.17.0
black==25.1.0
blessed==1.20.0
build==1.2.2.post1
certifi==2025.1.31
charset-normalizer==3.4.1
check-manifest==0.50
click==8.1.8
coverage==7.8.0
docutils==0.21.2
exceptiongroup==1.2.2
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
jarn.viewdoc==2.7
Jinja2==3.1.6
MarkupSafe==3.0.2
mypy-extensions==1.0.0
olefile==0.47
packaging==24.2
pathspec==0.12.1
-e git+https://github.com/python-pillow/Pillow.git@9bd691f1f4e1e53fff5b93a0d5d7a58bb15c5ef2#egg=Pillow
platformdirs==4.3.7
pluggy==1.5.0
pycodestyle==2.13.0
pyflakes==3.3.1
Pygments==2.19.1
pyproject_hooks==1.2.0
pyroma==4.2
pytest==8.3.5
pytest-cov==6.0.0
requests==2.32.3
six==1.17.0
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-removed-in==0.2.3
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.2.1
trove-classifiers==2025.3.19.19
typing_extensions==4.13.0
urllib3==2.3.0
wcwidth==0.2.13
zipp==3.21.0

name: Pillow
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - babel==2.17.0
      - black==25.1.0
      - blessed==1.20.0
      - build==1.2.2.post1
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - check-manifest==0.50
      - click==8.1.8
      - coverage==7.8.0
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jarn-viewdoc==2.7
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - mypy-extensions==1.0.0
      - olefile==0.47
      - packaging==24.2
      - pathspec==0.12.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pyproject-hooks==1.2.0
      - pyroma==4.2
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - requests==2.32.3
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-removed-in==0.2.3
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tomli==2.2.1
      - trove-classifiers==2025.3.19.19
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - wcwidth==0.2.13
      - zipp==3.21.0
prefix: /opt/conda/envs/Pillow

Tests/test_image.py::TestImage::test_open_formats
Tests/test_image.py::TestImage::test_no_resource_warning_on_save Tests/test_image.py::TestImage::test_show_deprecation
Tests/test_image.py::TestImage::test_image_modes_success Tests/test_image.py::TestImage::test_image_modes_fail Tests/test_image.py::TestImage::test_exception_inheritance Tests/test_image.py::TestImage::test_sanity Tests/test_image.py::TestImage::test_width_height Tests/test_image.py::TestImage::test_invalid_image Tests/test_image.py::TestImage::test_bad_mode Tests/test_image.py::TestImage::test_stringio Tests/test_image.py::TestImage::test_pathlib Tests/test_image.py::TestImage::test_fp_name Tests/test_image.py::TestImage::test_tempfile Tests/test_image.py::TestImage::test_unknown_extension Tests/test_image.py::TestImage::test_internals Tests/test_image.py::TestImage::test_readonly_save Tests/test_image.py::TestImage::test_dump Tests/test_image.py::TestImage::test_comparison_with_other_type Tests/test_image.py::TestImage::test_expand_x Tests/test_image.py::TestImage::test_expand_xy Tests/test_image.py::TestImage::test_getbands Tests/test_image.py::TestImage::test_getchannel_wrong_params Tests/test_image.py::TestImage::test_getchannel Tests/test_image.py::TestImage::test_getbbox Tests/test_image.py::TestImage::test_ne Tests/test_image.py::TestImage::test_alpha_composite Tests/test_image.py::TestImage::test_alpha_inplace Tests/test_image.py::TestImage::test_registered_extensions_uninitialized Tests/test_image.py::TestImage::test_registered_extensions Tests/test_image.py::TestImage::test_effect_mandelbrot Tests/test_image.py::TestImage::test_effect_mandelbrot_bad_arguments Tests/test_image.py::TestImage::test_effect_noise Tests/test_image.py::TestImage::test_effect_spread Tests/test_image.py::TestImage::test_check_size Tests/test_image.py::TestImage::test_storage_neg Tests/test_image.py::TestImage::test_linear_gradient_wrong_mode Tests/test_image.py::TestImage::test_linear_gradient Tests/test_image.py::TestImage::test_radial_gradient_wrong_mode Tests/test_image.py::TestImage::test_radial_gradient Tests/test_image.py::TestImage::test_register_extensions Tests/test_image.py::TestImage::test_remap_palette Tests/test_image.py::TestImage::test__new Tests/test_image.py::TestImage::test_p_from_rgb_rgba Tests/test_image.py::TestImage::test_showxv_deprecation Tests/test_image.py::TestImage::test_load_on_nonexclusive_multiframe Tests/test_image.py::TestImage::test_exif_jpeg Tests/test_image.py::TestImage::test_exif_webp Tests/test_image.py::TestImage::test_exif_png Tests/test_image.py::TestImage::test_exif_interop Tests/test_image.py::TestImage::test_pillow_version[PIL] Tests/test_image.py::TestImage::test_pillow_version[PIL.Image] Tests/test_image.py::TestImage::test_overrun Tests/test_image.py::TestRegistry::test_encode_registry Tests/test_image.py::TestRegistry::test_encode_registry_fail
src/PIL/Image.py docs/releasenotes/8.0.0.rst
src/PIL/Image.py docs/releasenotes/8.0.0.rst