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; }