diff --git a/config/schema/csp.schema.yml b/config/schema/csp.schema.yml
index 543c37d11d2e165803724c68879da62bab9b8c94..4de66eb983fb8f12048e59230ac42a3e737ff085 100644
--- a/config/schema/csp.schema.yml
+++ b/config/schema/csp.schema.yml
@@ -200,7 +200,9 @@ csp_directive.trusted-types:
       sequence:
         type: string
         constraints:
-          Regex: <^[a-z0-9#=_/@.%-]+$>i
+          Regex:
+            pattern: <^[a-z0-9#=_/@.%-]+$>i
+            message: '%value is not a valid trusted types policy name'
 csp_directive.require-trusted-types-for:
   type: sequence
   label: require-trusted-types-for
@@ -224,9 +226,16 @@ csp_reporting_handler.report-uri-com:
     subdomain:
       type: string
       label: 'Subdomain'
+      constraints:
+        NotBlank: []
+        Regex:
+          # Custom domains must be 4-30 characters, but generated domains are 32.
+          pattern: <^([a-z\d]{4,30}|[a-z\d]{32})$>i
+          message: Subdomain must be 4-30 alphanumeric characters.
     wizard:
       type: boolean
       label: 'Wizard'
+      requiredKey: false
 csp_reporting_handler.uri:
   type: mapping
   label: 'URI'
diff --git a/src/Form/CspSettingsForm.php b/src/Form/CspSettingsForm.php
index cc0ae460227b0abfec49d460623a91df450db8d4..94385dc25b4937eb0f2ebad821f4dd6010ba741c 100644
--- a/src/Form/CspSettingsForm.php
+++ b/src/Form/CspSettingsForm.php
@@ -6,12 +6,13 @@ use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\ConfigTarget;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\ToConfig;
 use Drupal\Core\Messenger\MessengerInterface;
-use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\csp\Csp;
 use Drupal\csp\LibraryPolicyBuilder;
-use Drupal\csp\Plugin\Validation\Constraint\SourceConstraint;
 use Drupal\csp\ReportingHandlerPluginManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\ConstraintViolationInterface;
@@ -154,7 +155,7 @@ class CspSettingsForm extends ConfigFormBase {
    * @return string[]
    *   An array of keywords.
    */
-  private function getKeywordOptions($directive): array {
+  private function getKeywordOptions(string $directive): array {
     return array_keys(array_filter(
       self::$keywordDirectiveMap,
       function ($directives) use ($directive) {
@@ -202,7 +203,10 @@ class CspSettingsForm extends ConfigFormBase {
       $form[$policyTypeKey]['enable'] = [
         '#type' => 'checkbox',
         '#title' => $this->t("Enable '@type'", ['@type' => $policyTypeName]),
-        '#default_value' => $config->get($policyTypeKey . '.enable') ?: FALSE,
+        '#config_target' => new ConfigTarget(
+          'csp.settings',
+          $policyTypeKey . '.enable',
+        ),
       ];
 
       $form[$policyTypeKey]['directives'] = [
@@ -218,6 +222,21 @@ class CspSettingsForm extends ConfigFormBase {
         $form[$policyTypeKey]['directives'][$directiveName] = [
           '#type' => 'container',
           '#access' => $policyTypeKey == 'enforce' || !in_array($directiveName, $enforceOnlyDirectives),
+          '#config_target' => new ConfigTarget(
+            'csp.settings',
+            $policyTypeKey . '.directives.' . $directiveName,
+            toConfig: match($directiveSchema) {
+              Csp::DIRECTIVE_SCHEMA_BOOLEAN => self::booleanToConfig(...),
+              Csp::DIRECTIVE_SCHEMA_TOKEN_LIST,
+              Csp::DIRECTIVE_SCHEMA_OPTIONAL_TOKEN_LIST => self::tokenListToConfig(...),
+              Csp::DIRECTIVE_SCHEMA_ALLOW_BLOCK => self::allowBlockToConfig(...),
+              Csp::DIRECTIVE_SCHEMA_SOURCE_LIST,
+              Csp::DIRECTIVE_SCHEMA_ANCESTOR_SOURCE_LIST => self::sourceListToConfig(...),
+              Csp::DIRECTIVE_SCHEMA_TRUSTED_TYPES =>  self::trustedTypesToConfig(...),
+              Csp::DIRECTIVE_SCHEMA_TRUSTED_TYPES_SINK_GROUPS => self::sinkGroupsToConfig(...),
+              default => self::directiveToConfig(...),
+            },
+          ),
         ];
 
         $form[$policyTypeKey]['directives'][$directiveName]['enable'] = [
@@ -322,6 +341,11 @@ class CspSettingsForm extends ConfigFormBase {
           '#title' => $this->t('Additional Sources'),
           '#description' => $this->t('Additional domains or protocols to allow for this directive, separated by a space.'),
           '#default_value' => implode(' ', $config->get($policyTypeKey . '.directives.' . $directiveName . '.sources') ?: []),
+          '#config_target' => new ConfigTarget(
+            'csp.settings',
+            $policyTypeKey . '.directives.' . $directiveName . '.sources',
+            toConfig: fn() => ToConfig::NoOp,
+          ),
           '#states' => [
             'visible' => [
               [':input[name="' . $policyTypeKey . '[directives][' . $directiveName . '][base]"]' => ['!value' => 'none']],
@@ -409,6 +433,11 @@ class CspSettingsForm extends ConfigFormBase {
         '#parents' => [$policyTypeKey, 'directives', 'trusted-types', 'policy-names'],
         '#title' => $this->t('Policy Names'),
         '#default_value' => implode(' ', $config->get($policyTypeKey . '.directives.trusted-types.policy-names') ?? []),
+        '#config_target' => new ConfigTarget(
+          'csp.settings',
+          $policyTypeKey . '.directives.trusted-types.policy-names',
+          toConfig: fn() => ToConfig::NoOp,
+        ),
         '#states' => [
           '!visible' => [
             [
@@ -439,6 +468,11 @@ class CspSettingsForm extends ConfigFormBase {
         '#type' => 'fieldset',
         '#title' => $this->t('Reporting'),
         '#tree' => TRUE,
+        '#config_target' => new ConfigTarget(
+          'csp.settings',
+          $policyTypeKey . '.reporting',
+          toConfig: self::reportingToConfig(...),
+        ),
       ];
       $form[$policyTypeKey]['reporting']['handler'] = [
         '#type' => 'radios',
@@ -518,6 +552,12 @@ class CspSettingsForm extends ConfigFormBase {
         }
 
         foreach ($directiveNames as $directive) {
+          if (
+            !str_starts_with($directive, 'script-src')
+            && !str_starts_with($directive, 'style-src')
+          ) {
+            continue;
+          }
           if (($directiveSources = $config->get($policyTypeKey . '.directives.' . $directive . '.sources'))) {
 
             // '{hashAlgorithm}-{base64-value}'
@@ -569,60 +609,7 @@ class CspSettingsForm extends ConfigFormBase {
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state): void {
-
-    /** @var \Symfony\Component\Validator\Validator\RecursiveValidator $sourceValidator */
-    $sourceValidator = \Drupal::service('validation.basic_recursive_validator_factory')->createValidator();
-
     foreach (['report-only', 'enforce'] as $policyTypeKey) {
-      $directiveNames = $this->getConfigurableDirectives();
-      foreach ($directiveNames as $directiveName) {
-        if (($directiveSources = $form_state->getValue([$policyTypeKey, 'directives', $directiveName, 'sources']))) {
-          $validationContext = $sourceValidator->startContext();
-          $sourcesArray = preg_split('/,?\s+/', $directiveSources);
-          foreach ($sourcesArray as $source) {
-            $validationContext->validate($source, new SourceConstraint());
-          }
-          if ($validationContext->getViolations()->count()) {
-            $form_state->setError(
-              $form[$policyTypeKey]['directives'][$directiveName]['options']['sources'],
-              new PluralTranslatableMarkup(
-                $validationContext->getViolations()->count(),
-                'Invalid source value for %directive: %value',
-                'Invalid source values for %directive: %value',
-                [
-                  '%directive' => $directiveName,
-                  '%value' => implode(', ', array_map(
-                    function (ConstraintViolationInterface $violation): string {
-                      return $violation->getInvalidValue();
-                    },
-                    iterator_to_array($validationContext->getViolations())
-                  )),
-                ]
-              )
-            );
-          }
-        }
-      }
-
-      // Check that trusted types policy names are valid.
-      // See https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive.
-      if (
-        $form_state->getValue([$policyTypeKey, 'directives', 'trusted-types', 'enable'])
-        && $form_state->getValue([$policyTypeKey, 'directives', 'trusted-types', 'base']) === ''
-        && ($trusted_type_policy_names = trim($form_state->getValue([$policyTypeKey, 'directives', 'trusted-types', 'policy-names'])))
-      ) {
-        $trusted_type_policy_names_array = preg_split('/,?\s+/', $trusted_type_policy_names);
-        $invalid_policy_names = array_filter($trusted_type_policy_names_array, function ($expression) {
-          return !preg_match('<^[a-z0-9#=_/@.%-]+$>i', $expression);
-        });
-        if (!empty($invalid_policy_names)) {
-          $form_state->setError(
-            $form[$policyTypeKey]['directives']['trusted-types']['options']['policy-names'],
-            $this->t('Invalid Trusted Types policy names: "%types".', ['%types' => implode(' ', $invalid_policy_names)])
-          );
-        }
-      }
-
       if (($reportingHandlerPluginId = $form_state->getValue([$policyTypeKey, 'reporting', 'handler']))) {
         if ($this->reportingHandlerPluginManager->hasDefinition($reportingHandlerPluginId)) {
           $this->reportingHandlerPluginManager->createInstance($reportingHandlerPluginId, ['type' => $policyTypeKey])
@@ -641,6 +628,62 @@ class CspSettingsForm extends ConfigFormBase {
     parent::validateForm($form, $form_state);
   }
 
+  /**
+   * {@inheritdoc}
+   *
+   * @param string $form_element_name
+   *   The form element for which to format multiple violation messages.
+   * @param array<ConstraintViolationInterface> $violations
+   *   The list of constraint violations that apply to this form element.
+   */
+  protected function formatMultipleViolationsMessage(string $form_element_name, array $violations): TranslatableMarkup {
+    /** @var array{("report-only"|"enforce"), string, string, string} $elementKeys */
+    $elementKeys = explode('][', $form_element_name);
+
+    $messageArgs = fn () => [
+      '%policy' => match($elementKeys[0]) {
+        'report-only' => $this->t('Report Only'),
+        'enforce' => $this->t('Enforced'),
+      },
+      '%directive' => $elementKeys[2],
+      '%value' => implode(', ', array_map(
+        function (ConstraintViolationInterface $violation): string {
+          return $violation->getInvalidValue();
+        },
+        $violations
+      )),
+    ];
+
+    if (str_ends_with($form_element_name, 'sources')) {
+      return $this->formatPlural(
+        count($violations),
+        'Invalid %policy %directive source: %value',
+        'Invalid %policy %directive sources: %value',
+        $messageArgs()
+      );
+    }
+    elseif (str_ends_with($form_element_name, 'trusted-types][policy-names')) {
+      return $this->formatPlural(
+        count($violations),
+        'Invalid %policy Trusted Types Policy Name: %value',
+        'Invalid %policy Trusted Types Policy Names: %value',
+        $messageArgs()
+      );
+    }
+
+    // Drupal ^11.0.6 returns MarkupInterface|\Stringable, which need conversion
+    // on prior versions to respect TranslatableMarkup type hint.
+    $parent = parent::formatMultipleViolationsMessage($form_element_name, $violations);
+    // @phpstan-ignore-next-line
+    if ($parent instanceof TranslatableMarkup) {
+      return $parent;
+    }
+    else {
+      // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
+      return $this->t((string) $parent);
+    }
+  }
+
   /**
    * Submit handler for clear policy buttons.
    *
@@ -658,97 +701,151 @@ class CspSettingsForm extends ConfigFormBase {
   }
 
   /**
-   * {@inheritdoc}
+   * Convert form state to config array for directive options.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return array<string, mixed>|\Drupal\Core\Form\ToConfig
+   *   Directive config array, or a ToConfig enum value.
    */
-  public function submitForm(array &$form, FormStateInterface $form_state): void {
-    $config = $this->config('csp.settings');
+  private static function directiveToConfig(array $value): array|ToConfig {
+    if (!$value['enable']) {
+      return ToConfig::DeleteKey;
+    }
+    unset($value['enable']);
 
-    $directiveNames = $this->getConfigurableDirectives();
-    foreach (['report-only', 'enforce'] as $policyTypeKey) {
-      $config->clear($policyTypeKey);
+    return $value;
+  }
 
-      $policyFormData = $form_state->getValue($policyTypeKey);
+  /**
+   * Convert form state to config value for a boolean directive.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return bool|\Drupal\Core\Form\ToConfig
+   *   Directive config value, or a ToConfig enum value.
+   */
+  private static function booleanToConfig(array $value): bool|ToConfig {
+    return !empty($value['enable']) ? TRUE : ToConfig::DeleteKey;
+  }
 
-      $config->set($policyTypeKey . '.enable', !empty($policyFormData['enable']));
+  /**
+   * Convert form state to config value for a token list directive.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return array<string>|\Drupal\Core\Form\ToConfig
+   *   Directive config array, or a ToConfig enum value.
+   */
+  private static function tokenListToConfig(array $value): array|ToConfig {
+    if (!$value['enable']) {
+      return ToConfig::DeleteKey;
+    }
+    unset($value['enable']);
 
-      foreach ($directiveNames as $directiveName) {
-        if (empty($policyFormData['directives'][$directiveName])) {
-          continue;
-        }
+    return array_keys(array_filter($value['keys']));
+  }
 
-        $directiveFormData = $policyFormData['directives'][$directiveName];
-        $directiveOptions = [];
+  /**
+   * Convert form state to config value for an allow/block directive.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return string|\Drupal\Core\Form\ToConfig
+   *   Directive config array, or a ToConfig enum value.
+   */
+  private static function allowBlockToConfig(array $value): string|ToConfig {
+    return !empty($value['enable']) ? $value['value'] : ToConfig::DeleteKey;
+  }
 
-        if (empty($directiveFormData['enable'])) {
-          continue;
-        }
+  /**
+   * Convert form state to config array for source list directive.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return array<string, mixed>|\Drupal\Core\Form\ToConfig
+   *   Directive config array, or a ToConfig enum value.
+   */
+  private static function sourceListToConfig(array $value): array|ToConfig {
+    if (!$value['enable']) {
+      return ToConfig::DeleteKey;
+    }
+    unset($value['enable']);
 
-        $directiveSchema = Csp::getDirectiveSchema($directiveName);
+    if ($value['base'] === 'none') {
+      return ['base' => 'none'];
+    }
 
-        if ($directiveSchema === Csp::DIRECTIVE_SCHEMA_BOOLEAN) {
-          $directiveOptions = TRUE;
-        }
-        elseif (in_array($directiveSchema, [
-          Csp::DIRECTIVE_SCHEMA_TOKEN_LIST,
-          Csp::DIRECTIVE_SCHEMA_OPTIONAL_TOKEN_LIST,
-        ])) {
-          $directiveOptions = array_keys(array_filter($directiveFormData['keys']));
-        }
-        elseif ($directiveSchema === Csp::DIRECTIVE_SCHEMA_ALLOW_BLOCK) {
-          $directiveOptions = $directiveFormData['value'];
-        }
-        elseif (in_array($directiveSchema, [
-          Csp::DIRECTIVE_SCHEMA_SOURCE_LIST,
-          Csp::DIRECTIVE_SCHEMA_ANCESTOR_SOURCE_LIST,
-        ])) {
-          if ($directiveFormData['base'] !== 'none') {
-            if (!empty($directiveFormData['sources'])) {
-              $directiveOptions['sources'] = array_filter(preg_split('/,?\s+/', $directiveFormData['sources']));
-            }
-            if ($directiveSchema == Csp::DIRECTIVE_SCHEMA_SOURCE_LIST) {
-              $directiveFormData['flags'] = array_filter($directiveFormData['flags']);
-              if (!empty($directiveFormData['flags'])) {
-                $directiveOptions['flags'] = array_keys($directiveFormData['flags']);
-              }
-            }
-          }
+    $value['sources'] = array_filter(preg_split('/,?\s+/', $value['sources']));
 
-          $directiveOptions['base'] = $directiveFormData['base'];
-        }
-        elseif ($directiveSchema == Csp::DIRECTIVE_SCHEMA_TRUSTED_TYPES) {
-          $directiveOptions['base'] = $directiveFormData['base'];
-          $directiveOptions['allow-duplicates'] = $directiveFormData['base'] !== 'none' && $directiveFormData['allow-duplicates'];
-          $directiveOptions['policy-names'] = $directiveOptions['base'] === '' ?
-            array_filter(preg_split('/,?\s+/', $directiveFormData['policy-names'])) :
-            [];
-        }
-        elseif ($directiveSchema == Csp::DIRECTIVE_SCHEMA_TRUSTED_TYPES_SINK_GROUPS) {
-          // Script is currently the only valid value, so set it when directive
-          // is enabled.
-          // See https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-csp-directive.
-          $directiveOptions = ['script'];
-        }
+    if (array_key_exists('flags', $value)) {
+      $value['flags'] = array_keys(array_filter($value['flags']));
+    }
 
-        if (
-          !empty($directiveOptions)
-          ||
-          $directiveSchema === Csp::DIRECTIVE_SCHEMA_OPTIONAL_TOKEN_LIST
-        ) {
-          $config->set($policyTypeKey . '.directives.' . $directiveName, $directiveOptions);
-        }
-      }
+    return array_filter($value);
+  }
 
-      $reportHandlerPluginId = $form_state->getValue([$policyTypeKey, 'reporting', 'handler']);
-      $config->set($policyTypeKey . '.reporting', ['plugin' => $reportHandlerPluginId]);
-      $reportHandlerOptions = $form_state->getValue([$policyTypeKey, 'reporting', $reportHandlerPluginId]);
-      if ($reportHandlerOptions) {
-        $config->set($policyTypeKey . '.reporting.options', $reportHandlerOptions);
-      }
+  /**
+   * Convert form state to config array for trusted types directive.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return array<string, mixed>|\Drupal\Core\Form\ToConfig
+   *   Directive config array, or a ToConfig enum value.
+   */
+  private static function trustedTypesToConfig(array $value): array|ToConfig {
+    if (!$value['enable']) {
+      return ToConfig::DeleteKey;
     }
+    unset($value['enable']);
+
+    $value['allow-duplicates'] = $value['base'] !== 'none' && $value['allow-duplicates'];
+    $value['policy-names'] = $value['base'] === '' ?
+      array_filter(preg_split('/,?\s+/', $value['policy-names'])) :
+      [];
+
+    return $value;
+  }
+
+  /**
+   * Convert form state to config array for trusted types sink groups.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return array<string>|\Drupal\Core\Form\ToConfig
+   *   Directive config array, or a ToConfig enum value.
+   */
+  private static function sinkGroupsToConfig(array $value): array|ToConfig {
+    // Script is currently the only valid value, so set it when enabled.
+    // See https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-csp-directive.
+    return !empty($value['enable']) ? ['script'] : ToConfig::DeleteKey;
+  }
 
-    $config->save();
+  /**
+   * Convert form state to config array for reporting options.
+   *
+   * @param array<string, mixed> $value
+   *   The submitted form values.
+   *
+   * @return array<string, mixed>
+   *   Reporting config array.
+   */
+  private static function reportingToConfig(array $value): array {
+    $return = [
+      'plugin' => $value['handler'],
+    ];
+    if (!empty($value[$value['handler']])) {
+      $return['options'] = $value[$value['handler']];
+    }
 
-    parent::submitForm($form, $form_state);
+    return $return;
   }
 
 }
diff --git a/src/Plugin/CspReportingHandler/None.php b/src/Plugin/CspReportingHandler/None.php
index c1f4c5404793e2e5cd68baa71691fbdcd7e0fe37..910f7e56f58d51b4c3d167d111a4e08cbde77b0a 100644
--- a/src/Plugin/CspReportingHandler/None.php
+++ b/src/Plugin/CspReportingHandler/None.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\csp\Plugin\CspReportingHandler;
 
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\csp\Csp;
 use Drupal\csp\Plugin\ReportingHandlerBase;
 
 /**
@@ -17,25 +15,4 @@ use Drupal\csp\Plugin\ReportingHandlerBase;
  */
 class None extends ReportingHandlerBase {
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getForm(array $form): array {
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state): void {
-
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function alterPolicy(Csp $policy): void {
-
-  }
-
 }
diff --git a/src/Plugin/CspReportingHandler/ReportUri.php b/src/Plugin/CspReportingHandler/ReportUri.php
index 78f1db467bf8726731bc20ff153e45ac37d8c59a..b84c5edb6ce357a00b00de7f51fd991516a2e644 100644
--- a/src/Plugin/CspReportingHandler/ReportUri.php
+++ b/src/Plugin/CspReportingHandler/ReportUri.php
@@ -2,7 +2,8 @@
 
 namespace Drupal\csp\Plugin\CspReportingHandler;
 
-use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\ConfigTarget;
+use Drupal\Core\Form\ToConfig;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\csp\Csp;
 use Drupal\csp\Plugin\ReportingHandlerBase;
@@ -34,6 +35,11 @@ class ReportUri extends ReportingHandlerBase {
         ':url' => 'https://report-uri.com/account/setup/',
       ]),
       '#default_value' => $this->configuration['subdomain'] ?? '',
+      '#config_target' => new ConfigTarget(
+        'csp.settings',
+        $this->configuration['type'] . '.reporting.options.subdomain',
+        toConfig: fn() => ToConfig::NoOp,
+      ),
       '#states' => [
         'required' => [
           ':input[name="' . $this->configuration['type'] . '[enable]"]' => ['checked' => TRUE],
@@ -58,17 +64,6 @@ class ReportUri extends ReportingHandlerBase {
     return $form;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state): void {
-    $subdomain = $form_state->getValue($form['subdomain']['#parents']);
-    // Custom domains must be 4-30 characters, but generated domains are 32.
-    if (!preg_match('/^[a-z\d]{4,32}$/i', $subdomain)) {
-      $form_state->setError($form['subdomain'], 'Must be 4-30 alphanumeric characters.');
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/src/Plugin/Validation/Constraint/SourceConstraintValidator.php b/src/Plugin/Validation/Constraint/SourceConstraintValidator.php
index 0fc1e1a856ece0755d9375783e5cbac0090c730a..832ad0fc2cc9eb3aec685d29c7dcfe37a2cb9d8f 100644
--- a/src/Plugin/Validation/Constraint/SourceConstraintValidator.php
+++ b/src/Plugin/Validation/Constraint/SourceConstraintValidator.php
@@ -23,11 +23,13 @@ class SourceConstraintValidator extends ConstraintValidator {
     if (!$constraint->allowNonce && self::isValidNonce($value)) {
       $this->context->buildViolation("Nonce sources are not valid.")
         ->setInvalidValue($value)
+        ->setCause('nonce')
         ->addViolation();
     }
     elseif (!$constraint->allowHash && self::isValidHash($value)) {
       $this->context->buildViolation("Hash sources are not valid.")
         ->setInvalidValue($value)
+        ->setCause('hash')
         ->addViolation();
     }
     elseif (
@@ -36,8 +38,8 @@ class SourceConstraintValidator extends ConstraintValidator {
       && !($constraint->allowHash && self::isValidHash($value))
       && !($constraint->allowNonce && self::isValidNonce($value))
     ) {
-      $this->context->buildViolation('"@value" is not a valid source')
-        ->setParameter('@value', $value)
+      $this->context->buildViolation('"%value" is not a valid source')
+        ->setParameter('%value', $value)
         ->setInvalidValue($value)
         ->addViolation();
     }