From c58a7f3fe043c13b210251760025bd971456d5ce Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 2 Jun 2026 09:41:26 +0000 Subject: [PATCH] Allowlist user-supplied field names in HasSelectableFields The selectFields scope passed request-supplied field names straight to addSelect(), letting a client dictate the column names referenced in the query (contrary to Laravel's own guidance). Restrict the non-translatable path to columns that actually exist on the table; the translatable path stays gated by the model's developer-defined $translatable list. https://claude.ai/code/session_01EUUnydptJJw8Az5AAVQ1r3 --- src/Traits/HasSelectableFields.php | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Traits/HasSelectableFields.php b/src/Traits/HasSelectableFields.php index e64f3099..9ebc5a82 100644 --- a/src/Traits/HasSelectableFields.php +++ b/src/Traits/HasSelectableFields.php @@ -11,6 +11,9 @@ trait HasSelectableFields { + /** @var array> */ + private static array $selectableColumnsCache = []; + private function isFieldTranslatable(string $field): bool { /** @var array $translatable */ @@ -19,16 +22,39 @@ private function isFieldTranslatable(string $field): bool return in_array($field, $translatable, true); } + /** + * Real columns of the underlying table, used to allowlist requested fields. + * + * @param Builder $query + * @return list + */ + private function selectableColumns(Builder $query): array + { + $table = $this->getTable(); + + return self::$selectableColumnsCache[$table] ??= $query + ->getConnection() + ->getSchemaBuilder() + ->getColumnListing($table); + } + /** @param Builder $query */ #[Scope] protected function selectFields(Builder $query): void { $locale = request('locale', app()->getLocale()); $fields = explode(',', (string) request()->string('fields.'.$this->getTable())); + $allowedColumns = $this->selectableColumns($query); foreach ($fields as $field) { + $field = mb_trim($field); + if (! $this->isFieldTranslatable($field)) { - $query->addSelect($field); + // Never let a user-supplied value dictate a column name: only + // select columns that actually exist on the table. + if (in_array($field, $allowedColumns, true)) { + $query->addSelect($field); + } continue; }