diff --git a/typo3/sysext/core/Classes/Imaging/GraphicalFunctions.php b/typo3/sysext/core/Classes/Imaging/GraphicalFunctions.php
index b62311f991e1dcaef319f661ef3b9dbd19ddc262..20d7df93ce0ad388e6f03ef0cddb4bcbd7239913 100644
--- a/typo3/sysext/core/Classes/Imaging/GraphicalFunctions.php
+++ b/typo3/sysext/core/Classes/Imaging/GraphicalFunctions.php
@@ -2160,7 +2160,7 @@ class GraphicalFunctions
         }
         $command .= ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
         // re-apply colorspace-setting for the resulting image so colors don't appear to dark (sRGB instead of RGB)
-        $command .= ' -colorspace ' . $this->colorspace;
+        $command .= ' -colorspace ' . CommandUtility::escapeShellArgument($this->colorspace);
         $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
         if ($this->alternativeOutputKey) {
             $theOutputName = md5($command . $cropscale . PathUtility::basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
diff --git a/typo3/sysext/core/Classes/Mail/TransportFactory.php b/typo3/sysext/core/Classes/Mail/TransportFactory.php
index 32772ad3b3689ad64e6cd103f80d9f38c1fcdb83..ee04673225944744d3478b1d40e97dde99f2be77 100644
--- a/typo3/sysext/core/Classes/Mail/TransportFactory.php
+++ b/typo3/sysext/core/Classes/Mail/TransportFactory.php
@@ -27,6 +27,7 @@ use Symfony\Component\Mailer\Transport\TransportInterface;
 use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 use TYPO3\CMS\Core\Exception;
 use TYPO3\CMS\Core\Log\LogManagerInterface;
+use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -150,6 +151,10 @@ class TransportFactory implements SingletonInterface, LoggerAwareInterface
                 if ($mboxFile === '') {
                     throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] needs to be set when transport is set to "mbox".', 1294586645);
                 }
+                $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class);
+                if (!$fileNameValidator->isValid($mboxFile)) {
+                    throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] failed against deny-pattern', 1705312431);
+                }
                 // Create our transport
                 $transport = GeneralUtility::makeInstance(
                     MboxTransport::class,
diff --git a/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php b/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
index 88cbe253aebcbb39e4e3f5d25c87f973934e558e..668de2d4919f727df24d184ddb6b6d3b8ae4d8e2 100644
--- a/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
+++ b/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Imaging\GraphicalFunctions;
 use TYPO3\CMS\Core\Resource\FileInterface;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Utility\CommandUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Frontend\Imaging\GifBuilder;
@@ -286,18 +287,21 @@ class LocalCropScaleMaskHelper
      */
     protected function modifyImageMagickStripProfileParameters(string $parameters, array $configuration)
     {
+        if (!isset($configuration['stripProfile'])) {
+            return $parameters;
+        }
+
+        $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'] ?? [];
+        // Use legacy processor_stripColorProfileCommand setting if defined, otherwise
+        // use the preferred configuration option processor_stripColorProfileParameters
+        $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ??
+            implode(' ', array_map(CommandUtility::escapeShellArgument(...), $gfxConf['processor_stripColorProfileParameters'] ?? []));
+
         // Strips profile information of image to save some space:
-        if (isset($configuration['stripProfile'])) {
-            if (
-                $configuration['stripProfile']
-                && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] !== ''
-            ) {
-                $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] . $parameters;
-            } else {
-                $parameters .= '###SkipStripProfile###';
-            }
+        if ($configuration['stripProfile'] && $stripColorProfileCommand !== '') {
+            return $stripColorProfileCommand . $parameters;
         }
-        return $parameters;
+        return $parameters . '###SkipStripProfile###';
     }
 
     protected function isTemporaryFile(string $filePath): bool
diff --git a/typo3/sysext/core/Classes/Utility/CommandUtility.php b/typo3/sysext/core/Classes/Utility/CommandUtility.php
index df74cd8a671e45e63de32ef9e572161bf5cd2367..e1ec81d241a4376ad735e83a7ef440b0f8f02626 100644
--- a/typo3/sysext/core/Classes/Utility/CommandUtility.php
+++ b/typo3/sysext/core/Classes/Utility/CommandUtility.php
@@ -119,14 +119,18 @@ class CommandUtility
         }
         // strip profile information for thumbnails and reduce their size
         if ($parameters && $command !== 'identify') {
+            // Use legacy processor_stripColorProfileCommand setting if defined, otherwise
+            // use the preferred configuration option processor_stripColorProfileParameters
+            $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ??
+                implode(' ', array_map(CommandUtility::escapeShellArgument(...), $gfxConf['processor_stripColorProfileParameters'] ?? []));
             // Determine whether the strip profile action has be disabled by TypoScript:
             if ($gfxConf['processor_stripColorProfileByDefault']
-                && $gfxConf['processor_stripColorProfileCommand'] !== ''
+                && $stripColorProfileCommand !== ''
                 && $parameters !== '-version'
-                && !str_contains($parameters, $gfxConf['processor_stripColorProfileCommand'])
+                && !str_contains($parameters, $stripColorProfileCommand)
                 && !str_contains($parameters, '###SkipStripProfile###')
             ) {
-                $parameters = $gfxConf['processor_stripColorProfileCommand'] . ' ' . $parameters;
+                $parameters = $stripColorProfileCommand . ' ' . $parameters;
             } else {
                 $parameters = str_replace('###SkipStripProfile###', '', $parameters);
             }
@@ -137,7 +141,7 @@ class CommandUtility
         }
         // set interlace parameter for convert command
         if ($command !== 'identify' && $gfxConf['processor_interlace']) {
-            $parameters = '-interlace ' . $gfxConf['processor_interlace'] . ' ' . $parameters;
+            $parameters = '-interlace ' . CommandUtility::escapeShellArgument($gfxConf['processor_interlace']) . ' ' . $parameters;
         }
         $cmdLine = $path . ' ' . $parameters;
         // It is needed to change the parameters order when a mask image has been specified
diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index c3a50ee72fcfc3ad30b55191908b079cb721a16d..a61c88d7485a769fb9b8b6e79b775daf67c98972 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -37,7 +37,7 @@ return [
         'processor_allowFrameSelection' => true,
         'processor_allowTemporaryMasksAsPng' => false,
         'processor_stripColorProfileByDefault' => true,
-        'processor_stripColorProfileCommand' => '+profile \'*\'',
+        'processor_stripColorProfileParameters' => ['+profile', '*'],
         'processor_colorspace' => 'RGB',
         'processor_interlace' => 'None',
         'jpg_quality' => 85,
diff --git a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
index a6ae8acf1cc29afc994962e811d706a96dbfc311..e2e5b10d16f536ff6fadc61c91c5f630b1258811 100644
--- a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
+++ b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
@@ -25,6 +25,7 @@ GFX:
             description: 'Enables the use of Image- or GraphicsMagick.'
         processor_path:
             type: text
+            readonly: true
             description: 'Path to the IM tools ''convert'', ''combine'', ''identify''.'
         processor:
             type: dropdown
@@ -46,10 +47,10 @@ GFX:
             description: 'This should be set if your processor supports using PNGs as masks as this is usually faster.'
         processor_stripColorProfileByDefault:
             type: bool
-            description: 'If set, the processor_stripColorProfileCommand is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.'
-        processor_stripColorProfileCommand:
-            type: text
-            description: 'String: Specify the command to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details'
+            description: 'If set, the processor_stripColorProfileParameters is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.'
+        processor_stripColorProfileParameters:
+            type: array
+            description: 'Comma separated list of parameters: Specify the parameters to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details'
         processor_colorspace:
             type: text
             description: 'String: Specify the colorspace to use. Some ImageMagick versions (like 6.7.0 and above) use the sRGB colorspace, so all images are darker then the original. <br />Possible Values: CMY, CMYK, Gray, HCL, HSB, HSL, HWB, Lab, LCH, LMS, Log, Luv, OHTA, Rec601Luma, Rec601YCbCr, Rec709Luma, Rec709YCbCr, RGB, sRGB, Transparent, XYZ, YCbCr, YCC, YIQ, YCbCr, YUV'
@@ -377,6 +378,7 @@ BE:
             description: 'Content-Security-Policy reporting HTTP endpoint, if empty system default will be used'
         fileDenyPattern:
             type: text
+            readonly: true
             description: 'A perl-compatible and JavaScript-compatible regular expression (without delimiters "/"!) that - if it matches a filename - will deny the file upload/rename or whatever. For security reasons, files with multiple extensions have to be denied on an Apache environment with mod_alias, if the filename contains a valid php handler in an arbitrary position. Also, ".htaccess" files have to be denied. Matching is done case-insensitive. Default value is stored in PHP constant FILE_DENY_PATTERN_DEFAULT'
         flexformForceCDATA:
             type: bool
@@ -627,6 +629,7 @@ MAIL:
         transport_sendmail_command:
             type: text
             description: '<em>only with transport=sendmail</em>: The command to call to send a mail locally.'
+            readonly: true
         transport_mbox_file:
             type: text
             description: '<em>only with transport=mbox</em>: The file where to write the mails into. This file will be conforming the mbox format described in RFC 4155. It is a simple text file with a concatenation of all mails. Path must be absolute.'
diff --git a/typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102799-TYPO3_CONF_VARSGFXprocessor_stripColorProfileParametersOptionAdded.rst b/typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102799-TYPO3_CONF_VARSGFXprocessor_stripColorProfileParametersOptionAdded.rst
new file mode 100644
index 0000000000000000000000000000000000000000..47155119947a3cc5d1c80d50438a8c2645fcb77e
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102799-TYPO3_CONF_VARSGFXprocessor_stripColorProfileParametersOptionAdded.rst
@@ -0,0 +1,40 @@
+.. include:: /Includes.rst.txt
+
+.. _important-102799-1707403491:
+
+===========================================================================================
+Important: #102799 - TYPO3_CONF_VARS.GFX.processor_stripColorProfileParameters option added
+===========================================================================================
+
+See :issue:`102799`
+
+Description
+===========
+
+The string-based configuration option
+:php:`$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']`
+has been superseded by
+:php:`$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters']`
+for security reasons.
+
+The former option expected a string of command line parameters. The defined
+parameters had to be shell-escaped beforehand, while the new option expects an
+array of strings that will be shell-escaped by TYPO3 when used.
+
+The existing configuration will continue to be supported. Still, it is suggested
+to use the new configuration format, as the Install Tool is adapted to allow
+modification of the new configuration option only:
+
+..  code-block:: php
+
+    // Before
+    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] = '+profile \'*\'';
+
+    // After
+    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters'] = [
+        '+profile',
+        '*'
+    ];
+
+
+.. index:: LocalConfiguration, ext:core
diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
index e09640db3227b6cf18b2b550a9b43d5b9607a2a9..e3cd55a3cc35cf7f76b6b4229296929da65238aa 100644
--- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
+++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
@@ -3821,7 +3821,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
                 }
 
                 // Possibility to cancel/force profile extraction
-                // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']
+                // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters']
                 if (isset($fileArray['stripProfile'])) {
                     $processingConfiguration['stripProfile'] = $fileArray['stripProfile'];
                 }
diff --git a/typo3/sysext/install/Classes/Configuration/AbstractCustomPreset.php b/typo3/sysext/install/Classes/Configuration/AbstractCustomPreset.php
index 89d7caa387256fcb09a297b4107c457399f0711d..dbdcf060baf2a14bfe961da72dcf032294426501 100644
--- a/typo3/sysext/install/Classes/Configuration/AbstractCustomPreset.php
+++ b/typo3/sysext/install/Classes/Configuration/AbstractCustomPreset.php
@@ -70,17 +70,29 @@ abstract class AbstractCustomPreset extends AbstractPreset
     }
 
     /**
-     * Get configuration values is used in fluid to show configuration options.
+     * Get configuration values is used to persist data and is merged with given $postValues.
+     *
+     * @return array Configuration values needed to activate prefix
+     */
+    public function getConfigurationValues()
+    {
+        return array_map(static fn($configuration) => $configuration['value'], $this->getConfigurationDescriptors());
+    }
+
+    /**
+     * Build configuration descriptors to be used in fluid to show configuration options.
      * They are fetched from LocalConfiguration / DefaultConfiguration and
      * merged with given $postValues.
      *
      * @return array Configuration values needed to activate prefix
      */
-    public function getConfigurationValues()
+    public function getConfigurationDescriptors()
     {
         $configurationValues = [];
         foreach ($this->configurationValues as $configurationKey => $configurationValue) {
-            if (isset($this->postValues['enable'])
+            $readonly = isset($this->readonlyConfigurationValues[$configurationKey]);
+            if (!$readonly
+                && isset($this->postValues['enable'])
                 && $this->postValues['enable'] === $this->name
                 && isset($this->postValues[$this->name][$configurationKey])
             ) {
@@ -88,7 +100,10 @@ abstract class AbstractCustomPreset extends AbstractPreset
             } else {
                 $currentValue = $this->configurationManager->getConfigurationValueByPath($configurationKey);
             }
-            $configurationValues[$configurationKey] = $currentValue;
+            $configurationValues[$configurationKey] = [
+                'value' => $currentValue,
+                'readonly' => $readonly,
+            ];
         }
         return $configurationValues;
     }
diff --git a/typo3/sysext/install/Classes/Configuration/AbstractPreset.php b/typo3/sysext/install/Classes/Configuration/AbstractPreset.php
index 57e4a182da0d2f05c0ba2c5656faf39632191fb6..3efd0c95859fef8234db86aa87b962ff4a4efefb 100644
--- a/typo3/sysext/install/Classes/Configuration/AbstractPreset.php
+++ b/typo3/sysext/install/Classes/Configuration/AbstractPreset.php
@@ -45,6 +45,11 @@ abstract class AbstractPreset implements PresetInterface
      */
     protected $configurationValues = [];
 
+    /**
+     * @var array Configuration values that are visible but not editable via presets GUI
+     */
+    protected $readonlyConfigurationValues = [];
+
     /**
      * @var array List of $POST values
      */
diff --git a/typo3/sysext/install/Classes/Configuration/Image/CustomPreset.php b/typo3/sysext/install/Classes/Configuration/Image/CustomPreset.php
index 67f3eeddb10bf2103bebeaf26d24c7223c6ac6a1..3336135d988e0149b2b77cfd25a89c91fddf54ec 100644
--- a/typo3/sysext/install/Classes/Configuration/Image/CustomPreset.php
+++ b/typo3/sysext/install/Classes/Configuration/Image/CustomPreset.php
@@ -35,4 +35,8 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface
         'GFX/processor_allowTemporaryMasksAsPng' => true,
         'GFX/processor_colorspace' => '',
     ];
+
+    protected $readonlyConfigurationValues = [
+        'GFX/processor_path' => true,
+    ];
 }
diff --git a/typo3/sysext/install/Classes/Configuration/Mail/CustomPreset.php b/typo3/sysext/install/Classes/Configuration/Mail/CustomPreset.php
index a6866752a3789b35a0c9ad50e7437b8827b70ab2..88e2bb22f6284afedb7049771a85bbff49205cd6 100644
--- a/typo3/sysext/install/Classes/Configuration/Mail/CustomPreset.php
+++ b/typo3/sysext/install/Classes/Configuration/Mail/CustomPreset.php
@@ -35,4 +35,8 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface
         'MAIL/transport_smtp_username' => '',
         'MAIL/transport_smtp_password' => '',
     ];
+
+    protected $readonlyConfigurationValues = [
+        'MAIL/transport_sendmail_command' => true,
+    ];
 }
diff --git a/typo3/sysext/install/Classes/Configuration/PasswordHashing/CustomPreset.php b/typo3/sysext/install/Classes/Configuration/PasswordHashing/CustomPreset.php
index cf1a0a433a317edfdbe0f5d3a8094e234d4e741e..8e01fb67b48a1e9b8b18c09bff480bfe4ffe7023 100644
--- a/typo3/sysext/install/Classes/Configuration/PasswordHashing/CustomPreset.php
+++ b/typo3/sysext/install/Classes/Configuration/PasswordHashing/CustomPreset.php
@@ -32,22 +32,35 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface
      * Get configuration values is used in fluid to show configuration options.
      * They are fetched from LocalConfiguration / DefaultConfiguration.
      *
+     * They are not merged with postValues for security reasons, as
+     * all options are readonly.
+     *
      * @return array Current custom configuration values
      */
-    public function getConfigurationValues(): array
+    public function getConfigurationDescriptors(): array
     {
         $configurationValues = [];
-        $configurationValues['BE/passwordHashing/className'] =
-            $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className');
+        $configurationValues['BE/passwordHashing/className'] = [
+            'value' => $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'),
+            'readonly' => true,
+        ];
         $options = (array)$this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/options');
         foreach ($options as $optionName => $optionValue) {
-            $configurationValues['BE/passwordHashing/options/' . $optionName] = $optionValue;
+            $configurationValues['BE/passwordHashing/options/' . $optionName] = [
+                'value' => $optionValue,
+                'readonly' => true,
+            ];
         }
-        $configurationValues['FE/passwordHashing/className'] =
-            $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className');
+        $configurationValues['FE/passwordHashing/className'] = [
+            'value' => $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'),
+            'readonly' => true,
+        ];
         $options = (array)$this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/options');
         foreach ($options as $optionName => $optionValue) {
-            $configurationValues['FE/passwordHashing/options/' . $optionName] = $optionValue;
+            $configurationValues['FE/passwordHashing/options/' . $optionName] = [
+                'value' => $optionValue,
+                'readonly' => true,
+            ];
         }
         return $configurationValues;
     }
diff --git a/typo3/sysext/install/Classes/Service/LocalConfigurationValueService.php b/typo3/sysext/install/Classes/Service/LocalConfigurationValueService.php
index d89195699e1e8f13de5ad2b98e7056fe6bdcd0a7..4f8068fa570e1129f6734debe9b41cdf7005ab00 100644
--- a/typo3/sysext/install/Classes/Service/LocalConfigurationValueService.php
+++ b/typo3/sysext/install/Classes/Service/LocalConfigurationValueService.php
@@ -21,6 +21,8 @@ use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
+use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
+use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -90,6 +92,7 @@ class LocalConfigurationValueService
                 $itemData['path'] = '[' . implode('][', $newPath) . ']';
                 $itemData['fieldType'] = $descriptionInfo['type'];
                 $itemData['description'] = $descriptionInfo['description'] ?? '';
+                $itemData['readonly'] = $descriptionInfo['readonly'] ?? false;
                 $itemData['allowedValues'] = $descriptionInfo['allowedValues'] ?? [];
                 $itemData['differentValueInCurrentConfiguration'] = (!isset($descriptionInfo['compareValuesWithCurrentConfiguration']) ||
                     $descriptionInfo['compareValuesWithCurrentConfiguration']) &&
@@ -151,11 +154,28 @@ class LocalConfigurationValueService
         $commentArray = $this->getDefaultConfigArrayComments();
         $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
         foreach ($valueList as $path => $value) {
-            $oldValue = $configurationManager->getConfigurationValueByPath($path);
+            try {
+                $oldValue = $configurationManager->getConfigurationValueByPath($path);
+            } catch (MissingArrayPathException) {
+                $messageQueue->enqueue(new FlashMessage(
+                    'Update rejected, the category of this setting does not exist',
+                    $path,
+                    ContextualFeedbackSeverity::ERROR
+                ));
+                continue;
+            }
             $pathParts = explode('/', $path);
             $descriptionData = $commentArray[$pathParts[0]];
 
             while ($part = next($pathParts)) {
+                if (!isset($descriptionData['items'][$part])) {
+                    $messageQueue->enqueue(new FlashMessage(
+                        'Update rejected, this setting is not writable',
+                        $path,
+                        ContextualFeedbackSeverity::ERROR
+                    ));
+                    continue 2;
+                }
                 $descriptionData = $descriptionData['items'][$part];
             }
 
@@ -183,6 +203,16 @@ class LocalConfigurationValueService
                 $valueHasChanged = (string)$oldValue !== (string)$value;
             }
 
+            $readonly = $descriptionData['readonly'] ?? false;
+            if ($readonly && $valueHasChanged) {
+                $messageQueue->enqueue(new FlashMessage(
+                    'Update rejected, this setting is readonly',
+                    $path,
+                    ContextualFeedbackSeverity::ERROR
+                ));
+                continue;
+            }
+
             // Save if value changed
             if ($valueHasChanged) {
                 $configurationPathValuePairs[$path] = $value;
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/LocalConfiguration/SubSection.html b/typo3/sysext/install/Resources/Private/Partials/Settings/LocalConfiguration/SubSection.html
index 0128fba88c06473d9d05daeb42e919e9211b6445..af67289832bb522037acc0be0bced45f610e9c86 100644
--- a/typo3/sysext/install/Resources/Private/Partials/Settings/LocalConfiguration/SubSection.html
+++ b/typo3/sysext/install/Resources/Private/Partials/Settings/LocalConfiguration/SubSection.html
@@ -32,6 +32,7 @@
                             </f:if>
                         </div>
                         <div class="localconf-item-body">
+                            <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: item}" />
                             <f:if condition="{item.differentValueInCurrentConfiguration}">
                                 <div class="t3js-infobox callout callout-warning">
                                     <div class="callout-body">
@@ -44,7 +45,9 @@
                                 <f:then>
                                     <div class="form-group">
                                         <div class="form-description">{item.description -> f:sanitize.html()}</div>
-                                        <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue" {f:if(condition: '!{isWritable}', then: 'disabled')}>
+                                        <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue"
+                                            {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')}
+                                            >
                                             <f:for each="{item.allowedValues}" key="optionKey" as="optionLabel">
                                                 <option value="{optionKey}" {f:if(condition: '{item.value} == {optionKey}', then: 'selected="selected"')}>{optionLabel} ({optionKey})</option>
                                             </f:for>
@@ -62,7 +65,7 @@
                                                 id="{sectionName}_{item.key}"
                                                 data-path="{sectionName}/{item.key}"
                                                 {f:if(condition: item.checked, then:'checked="checked"')}
-                                                {f:if(condition: '!{isWritable}', then: 'disabled')}
+                                                {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')}
                                             />
                                             <label class="form-check-label" for="{sectionName}_{item.key}">
                                                 {item.description -> f:sanitize.html()}
@@ -81,7 +84,7 @@
                                                 data-path="{sectionName}/{item.key}"
                                                 class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue"
                                                 autocomplete="off"
-                                                {f:if(condition: '!{isWritable}', then: 'disabled')}
+                                                {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')}
                                             />
                                         </div>
                                     </f:if>
@@ -97,7 +100,7 @@
                                                 data-path="{sectionName}/{item.key}"
                                                 class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue"
                                                 autocomplete="new-password"
-                                                {f:if(condition: '!{isWritable}', then: 'disabled')}
+                                                {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')}
                                             />
                                         </div>
                                     </f:if>
@@ -114,7 +117,7 @@
                                                 class="t3-
                                                 install-form-input-text form-control t3js-localConfiguration-pathValue"
                                                 autocomplete="off"
-                                                {f:if(condition: '!{isWritable}', then: 'disabled')}
+                                                {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')}
                                             />
                                         </div>
                                     </f:if>
@@ -129,7 +132,7 @@
                                                 cols="60"
                                                 data-path="{sectionName}/{item.key}"
                                                 class="form-control t3js-localConfiguration-pathValue"
-                                                {f:if(condition: '!{isWritable}', then: 'disabled')}
+                                                {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')}
                                             >{item.value}</textarea>
                                         </div>
                                     </f:if>
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Cache/Custom.html b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Cache/Custom.html
index 91e717f859a62d4287a2ffbbc5c90e467cf6a606..ee7f283b36e2f9bd8e51f5c2f07fbe3fa25d3ff7 100644
--- a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Cache/Custom.html
+++ b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Cache/Custom.html
@@ -16,18 +16,19 @@
         </label>
     </div>
     <p>Custom cache settings:</p>
-    <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey">
+    <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey">
         <div class="row mb-3">
             <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label>
             <div class="col-sm-6">
+                <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" />
                 <input
                     id="{feature.name}{preset.name}{configurationKey}"
                     type="text"
                     name="install[values][{feature.name}][{preset.name}][{configurationKey}]"
-                    value="{configurationValue}"
+                    value="{configuration.value}"
                     class="form-control t3js-custom-preset"
                     data-radio="t3-install-tool-configuration-cache-custom"
-                    {f:if(condition: '!{isWritable}', then: 'disabled')}
+                    {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')}
                     />
             </div>
         </div>
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Context/Custom.html b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Context/Custom.html
index 7638730b5908ded53754599f3d42d77c2fb8c26c..a599f5f925a805a23503de7fda2f5a29f7bbf1bc 100644
--- a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Context/Custom.html
+++ b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Context/Custom.html
@@ -17,18 +17,19 @@
     </div>
     <p>Custom configuration mixture if no other preset fits.</p>
 
-    <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey">
+    <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey">
         <div class="row mb-3">
             <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label>
             <div class="col-sm-8">
+                <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" />
                 <input
                     id="{feature.name}{preset.name}{configurationKey}"
                     type="text"
                     name="install[values][{feature.name}][{preset.name}][{configurationKey}]"
-                    value="{configurationValue}"
+                    value="{configuration.value}"
                     class="form-control t3js-custom-preset"
                     data-radio="t3-install-tool-configuration-context-custom"
-                    {f:if(condition: '!{isWritable}', then: 'disabled')}
+                    {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')}
                 />
             </div>
         </div>
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Image/Custom.html b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Image/Custom.html
index 07db6d9c57d62053ff7c245e57ea4473bc3cdd62..9d9e4a3cd8a66a758298f2162e123ac05fe1dd73 100644
--- a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Image/Custom.html
+++ b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Image/Custom.html
@@ -18,18 +18,19 @@
 
     <p>Custom configuration mixture if no other preset fits.</p>
 
-    <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey">
+    <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey">
         <div class="row mb-3">
             <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label>
             <div class="col-sm-8">
+                <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" />
                 <input
                     id="{feature.name}{preset.name}{configurationKey}"
                     type="text"
                     name="install[values][{feature.name}][{preset.name}][{configurationKey}]"
-                    value="{configurationValue}"
+                    value="{configuration.value}"
                     class="form-control t3js-custom-preset"
                     data-radio="t3-install-tool-configuration-image-custom"
-                    {f:if(condition: '!{isWritable}', then: 'disabled')}
+                    {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')}
                 />
             </div>
         </div>
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Mail/Custom.html b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Mail/Custom.html
index ebfa27c7a46c661c00f2db540bd09df759125ce2..3e8d5202754831e7216cded6e9dd64afce8c5fbb 100644
--- a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Mail/Custom.html
+++ b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Mail/Custom.html
@@ -16,19 +16,21 @@
         </label>
     </div>
     <p>Custom mail settings:</p>
-    <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey">
+
+    <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey">
         <div class="row mb-3">
             <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label>
             <div class="col-sm-6">
+                <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" />
                 <input
                     id="{feature.name}{preset.name}{configurationKey}"
                     type="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'password', else: 'text')}"
                     autocomplete="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'new-password', else: 'off')}"
                     name="install[values][{feature.name}][{preset.name}][{configurationKey}]"
-                    value="{configurationValue}"
+                    value="{configuration.value}"
                     class="form-control t3js-custom-preset"
                     data-radio="t3-install-tool-configuration-mail-custom"
-                    {f:if(condition: '!{isWritable}', then: 'disabled')}
+                    {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')}
                     />
             </div>
         </div>
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.html b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.html
index 5c26c80931f1b59de12d547c2ac4d3f0a0407f59..59cf77451f4438aa13c6ee49d994915b87df6f5d 100644
--- a/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.html
+++ b/typo3/sysext/install/Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.html
@@ -18,19 +18,19 @@
     <p>Custom password hash settings. This interface does not allow modification of the values, they are just shown.
         Configuring custom hash settings is for advanced users who know exactly what they are doing. Refer to the
         core documentation for details.</p>
-    <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey">
+    <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey">
         <div class="row mb-3">
             <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label>
             <div class="col-sm-6">
+                <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" />
                 <input
                     id="{feature.name}{preset.name}{configurationKey}"
                     type="text"
                     name="install[values][{feature.name}][{preset.name}][{configurationKey}]"
-                    value="{configurationValue}"
-                    disabled="disabled"
+                    value="{configuration.value}"
                     class="form-control t3js-custom-preset"
                     data-radio="t3-install-tool-configuration-passwordHashing-custom"
-                    {f:if(condition: '!{isWritable}', then: 'disabled')}
+                    {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')}
                     />
             </div>
         </div>
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/ReadonlyInfo.html b/typo3/sysext/install/Resources/Private/Partials/Settings/ReadonlyInfo.html
new file mode 100644
index 0000000000000000000000000000000000000000..5cc452328353eaf0d05286e78699ae4f40d5ab79
--- /dev/null
+++ b/typo3/sysext/install/Resources/Private/Partials/Settings/ReadonlyInfo.html
@@ -0,0 +1,10 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
+    <f:if condition="{configuration.readonly}">
+        <div class="t3js-infobox callout callout-info">
+            <div class="callout-body">
+                For security reasons, this option cannot be changed here.<br>
+                Please configure via <code>system/settings.php</code> or <code>system/additional.php</code>.
+            </div>
+        </div>
+    </f:if>
+</html>