| @@ -188,8 +188,26 @@ | ||
| 188 | 188 | $valueArray = $value ? json_decode($value, true) : []; |
| 189 | 189 | |
| 190 | 190 | $key = sanitize_text_field(Arr::get($attributes, 'meta_key')); |
| 191 | 191 | |
| 192 | + // SECURITY (FINDING-14): this generic settings store accepted an arbitrary meta_key with no | |
| 193 | + // allowlist, letting a forms_manager overwrite meta owned by dedicated, capability-gated | |
| 194 | + // endpoints — most importantly the unfiltered_html-gated custom JS/CSS keys (bypassing the | |
| 195 | + // boundary Customizer::store() enforces) and payment settings. Reject those keys here; each | |
| 196 | + // has its own proper route. Filterable so first-party code can extend the protected set. | |
| 197 | + $protectedKeys = apply_filters('fluentform/protected_form_meta_keys', [ | |
| 198 | + '_custom_form_js', | |
| 199 | + '_custom_form_css', | |
| 200 | + '_payment_settings', | |
| 201 | + ]); | |
| 202 | + if (in_array($key, $protectedKeys, true)) { | |
| 203 | + throw new \FluentForm\Framework\Validator\ValidationException('', 422, null, [ | |
| 204 | + 'errors' => [ | |
| 205 | + 'meta_key' => [__('This settings key cannot be modified from this endpoint.', 'fluentform')], | |
| 206 | + ], | |
| 207 | + ]); | |
| 208 | + } | |
| 209 | + | |
| 192 | 210 | if ('formSettings' == $key) { |
| 193 | 211 | Validator::validate( |
| 194 | 212 | 'confirmations', |
| 195 | 213 | Arr::get( |