Skip to content

Allowlist user-supplied field names in HasSelectableFields#107

Open
sdebacker wants to merge 1 commit into
mainfrom
claude/allowlist-selectable-fields
Open

Allowlist user-supplied field names in HasSelectableFields#107
sdebacker wants to merge 1 commit into
mainfrom
claude/allowlist-selectable-fields

Conversation

@sdebacker

Copy link
Copy Markdown
Contributor

Summary

The selectFields scope (used by the Pages/Tags/Terms/Menus/… API controllers) built its SELECT list from the request-supplied fields[<table>] parameter and passed each value directly to addSelect():

$fields = explode(',', (string) request()->string('fields.'.$this->getTable()));

foreach ($fields as $field) {
    if (! $this->isFieldTranslatable($field)) {
        $query->addSelect($field); // ← user controls the column name
        ...

Letting user input dictate column names goes against Laravel's own guidance ("you should never allow user input to dictate the column names referenced by your queries"). Eloquent wraps identifiers, so this isn't trivially injectable on MySQL, but it's a fragile, defense-relies-on-the-grammar pattern and lets clients reference columns the endpoint never intended to expose.

Fix

Restrict the non-translatable path to columns that actually exist on the table (resolved once per table via the schema builder and cached). Unknown/empty values are skipped instead of being handed to addSelect().

The translatable path is unchanged — it was already gated by the model's developer-defined $translatable list (not user input), and its raw SQL uses bound parameters for the locale.

Behavior for legitimate requests is unchanged; only unknown/crafted field names are dropped.

Verification

Logic exercised standalone:

Requested fields[...] Result
id,created_at,position selects those columns
title,status,slug (translatable) JSON-extract selects (unchanged)
id, created_at , title whitespace tolerated
id,(SELECT api_token FROM users) only id selected; the rest dropped
password,secret_column (non-existent) dropped

php -l passes (PHP 8.4). The package has no vendor/ in this environment (the suite runs against a host app), so the full test suite wasn't run here.

https://claude.ai/code/session_01EUUnydptJJw8Az5AAVQ1r3


Generated by Claude Code

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
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.

2 participants