Skip to content

[6.x] Relax group/grid fieldtype check in ReplicatorController - #14068

Merged
jasonvarga merged 6 commits into
6.xfrom
replicator-inside-custom-fieldtypes
Sep 11, 2026
Merged

[6.x] Relax group/grid fieldtype check in ReplicatorController#14068
jasonvarga merged 6 commits into
6.xfrom
replicator-inside-custom-fieldtypes

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request relaxes the group/grid fieldtype check in ReplicatorController, fixing an issue where Bard/Replicator sets couldn't be added when nested inside a custom fieldtype.

Fixes #14066

@jasonvarga

Copy link
Copy Markdown
Member

I think we explicitly checked for the fieldtypes for a reason. I could be misremembering though. I'm probably wrong and this is a correct fix but I want to check the history and do some more testing before merging.

@Narsileon

Copy link
Copy Markdown

Hello,

is there any update on this issue? We are experiencing the same error as well.

Thank you in advance!

@jasonvarga jasonvarga left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Duncan — the underlying fix is right, but a few things need sorting before this can go in.

The branch merging 6.x pulled in the endpoint hardening from #14255, and this PR's new test wasn't converted along with the rest of the file, so CI is red across nearly every PHP shard.

Separately, on my earlier comment about why we checked the fieldtypes explicitly — I dug into it. The type whitelist wasn't protecting us from addon fieldtypes with an unrelated fields key (that shape was already broken before this PR, and nothing in core has one — only Group and Grid persist a top-level fields key). What it was protecting us from is that $config isn't always a field config. Details inline.

Comment thread tests/Fieldtypes/ReplicatorTest.php Outdated
Comment thread src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php Outdated
Comment thread src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php Outdated
duncanmcclean and others added 3 commits September 11, 2026 08:29
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011yxrHxZC18iQiRgJgDjccK
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011yxrHxZC18iQiRgJgDjccK
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011yxrHxZC18iQiRgJgDjccK
@duncanmcclean

Copy link
Copy Markdown
Member Author

Thanks for digging into the history, that explains it.

  • Converted the new test to the encrypted token payload like its siblings.
  • Added the type guard so the flattened sets map never takes the nested-fields branch, plus a regression test for a Replicator with a set handled fields (it 500'd with the unguarded check).
  • Renamed $isGroupOrGrid to $hasNestedFields.

@Devsome

Devsome commented Sep 11, 2026

Copy link
Copy Markdown

Hi everyone,

I managed to fix it using this custom “PR” and the composer-patches package to inject it. Maybe it'll help you, if you still need it.

Code
diff --git a/resources/js/components/fieldtypes/replicator/Replicator.vue b/resources/js/components/fieldtypes/replicator/Replicator.vue
index d2843c3f0..6cef1a737 100644
--- a/resources/js/components/fieldtypes/replicator/Replicator.vue
+++ b/resources/js/components/fieldtypes/replicator/Replicator.vue
@@ -274,17 +274,20 @@ export default {
 
             return this.fieldPathKeys
                 .map((key, index) => {
-					if (['attrs', 'values'].includes(key)) return;
+                    if (['attrs', 'values'].includes(key) || key === '') return;
 
-                    if (Number.isInteger(parseInt(key))) {
-	                    let setValues =  data_get(this.publishContainer.values, this.fieldPathKeys.slice(0, index + 1).join('.'));
+                    if (/^\d+$/.test(key)) {
+                        let setValues = data_get(
+                            this.publishContainer.values,
+                            this.fieldPathKeys.slice(0, index + 1).join('.'),
+                        );
 
-	                    return setValues.attrs?.values.type || setValues.type;
+                        return setValues?.attrs?.values?.type || setValues?.type || undefined;
                     }
 
                     return key;
                 })
-                .filter((key) => key !== undefined)
+                .filter((key) => !!key)
                 .concat(this.handle)
                 .join('.');
         },
diff --git a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php
index fa7c946ae..156efef1d 100644
--- a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php
+++ b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php
@@ -4,7 +4,6 @@ namespace Statamic\Http\Controllers\CP\Fieldtypes;
 
 use Illuminate\Contracts\Encryption\DecryptException;
 use Illuminate\Http\Request;
-use Illuminate\Support\Str;
 use Statamic\Exceptions\NotFoundHttpException;
 use Statamic\Facades;
 use Statamic\Facades\Data;
@@ -70,17 +69,28 @@ class ReplicatorSetController extends CpController
 
     private function getReplicatorField(Blueprint $blueprint, string $field): Field
     {
-        $remainingFieldPathComponents = explode('.', $field);
+        $remainingFieldPathComponents = array_values(array_filter(
+            explode('.', $field),
+            fn ($component) => $component !== ''
+        ));
 
-        $config = $blueprint->fields()->all()->get($remainingFieldPathComponents[0])->config();
+        if ($remainingFieldPathComponents === []) {
+            throw new \Exception("Cannot find Replicator field [$field]");
+        }
+
+        $blueprintField = $blueprint->fields()->all()->get($remainingFieldPathComponents[0]);
+
+        if (! $blueprintField) {
+            throw new \Exception("Cannot find Replicator field [$field]");
+        }
 
-        $config = $this->getConfig($config, $remainingFieldPathComponents);
+        $config = $this->getConfig($blueprintField->config(), $remainingFieldPathComponents);
 
         if (! isset($config['type'])) {
             throw new \Exception("Cannot find Replicator field [$field]");
         }
 
-        return new Field(Str::afterLast($field, '.'), $config);
+        return new Field($remainingFieldPathComponents[array_key_last($remainingFieldPathComponents)], $config);
     }
 
     private function getConfig(array $config, array $remainingFieldPathComponents): array
@@ -103,16 +113,41 @@ class ReplicatorSetController extends CpController
         if ($isGroupOrGrid) {
             array_shift($remainingFieldPathComponents);
 
+            if ($remainingFieldPathComponents === []) {
+                return $config;
+            }
+
             $fields = $this->resolveFields($config['fields'] ?? []);
+            $handle = $remainingFieldPathComponents[0];
 
-            return $this->getConfig($fields[$remainingFieldPathComponents[0]]['field'], $remainingFieldPathComponents);
+            if (! isset($fields[$handle]['field'])) {
+                throw new \Exception("Cannot find field [{$handle}]");
+            }
+
+            return $this->getConfig($fields[$handle]['field'], $remainingFieldPathComponents);
         }
 
-        $fields = $this->resolveFields($config[$remainingFieldPathComponents[0]]['fields']);
+        $setHandle = $remainingFieldPathComponents[0] ?? null;
+
+        if ($setHandle === null || $setHandle === '' || ! isset($config[$setHandle]['fields'])) {
+            throw new \Exception("Cannot find Replicator set [{$setHandle}]");
+        }
+
+        $fields = $this->resolveFields($config[$setHandle]['fields']);
 
         array_shift($remainingFieldPathComponents);
 
-        return $this->getConfig($fields[$remainingFieldPathComponents[0]]['field'], $remainingFieldPathComponents);
+        if ($remainingFieldPathComponents === []) {
+            throw new \Exception("Cannot find nested field in Replicator set [{$setHandle}]");
+        }
+
+        $handle = $remainingFieldPathComponents[0];
+
+        if (! isset($fields[$handle]['field'])) {
+            throw new \Exception("Cannot find field [{$handle}]");
+        }
+
+        return $this->getConfig($fields[$handle]['field'], $remainingFieldPathComponents);
     }
 
     private function flattenSets(array $sets): array

@jasonvarga
jasonvarga merged commit 07e518a into 6.x Sep 11, 2026
64 checks passed
@jasonvarga
jasonvarga deleted the replicator-inside-custom-fieldtypes branch September 11, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReplicatorSetController doesn't support custom container fieldtypes in field path traversal

4 participants