Skip to content

fix: a data attribute is escaped like everything else the theme renders - #927

Merged
blaipr merged 1 commit into
mainfrom
fix/a-data-attribute-is-escaped-like-everything-else
Sep 17, 2026
Merged

blaipr merged 1 commit into
mainfrom
fix/a-data-attribute-is-escaped-like-everything-else

Conversation

@blaipr

@blaipr blaipr commented Sep 17, 2026

Copy link
Copy Markdown
Member

Eight places in four templates emitted data-<name>="<value>" out of a
DataGridAction::getData() / getRuntimeData() map with no escaping at all — not $_e(), not
anything:

printf('data-%s="%s"', $dataName, $dataValue);
echo 'data-', $dataName, '=', '"', $dataValue, '"';

Nothing is leaking today: every current addData()/setRuntimeData() call passes an ACL route
string or an integer id. The one value that is not a literal is FileGrid's item-type, a MIME type
that AllowedMimeType::resolve() requires to match the administrator's own allow-list exactly, so
putting a quote there is self-inflicted.

But nothing in the templates or in DataGridActionBase constrains what a caller may pass, and one
addData() carrying a name, a login or a note breaks straight out of the attribute. data-onclick
was unescaped the same way.

Why the existing guard could not see it

ThemeEscapesWhatItRendersTest decides what carries text by the getter's name (getName,
getLogin, …) or by $_getvar(. A bare $dataValue in a loop matches neither, so the rule that
holds the rest of the theme to escaping-on-output had a blind spot exactly where a caller-supplied
map is rendered.

aDataAttributeMapIsEscaped() closes it, narrowly: it looks only at the loops whose attribute
name is itself a variable — which are precisely the ones taking an arbitrary map — and requires
$_e( in the emitting expression. A fixed data-item-id="<?php echo $id; ?>" is an id by
construction and stays with the rule above.

Writing the guard found more than the audit did

The review that started this named three sites, in datagrid-rows.inc and search-rows.inc. The
guard, run against the source, found five more: two in _partials/fixed-header.inc and three in
grid/datagrid-grid.inc. That is the whole argument for scanning rather than fixing a list.

Tests

Mutation-verified: reverting public/ fails the guard on four templates, naming all sixteen
occurrences.

Eight places in four templates emitted data-<name>="<value>" out of a
DataGridAction::getData()/getRuntimeData() map with no escaping at all. Nothing leaks
today — every current addData() passes an ACL route or an integer id, and FileGrid's
item-type must match the administrator's own MIME allow-list exactly — but nothing in the
templates or in DataGridActionBase constrains what a caller may pass, and one addData()
carrying a name, a login or a note breaks straight out of the attribute. data-onclick was
unescaped the same way.

ThemeEscapesWhatItRendersTest decides what carries text by the getter's name or by
$_getvar(, so a bare $dataValue in a loop matched neither: the rule that holds the rest of
the theme to escaping-on-output had a blind spot exactly where a caller-supplied map is
rendered. aDataAttributeMapIsEscaped() closes it narrowly — only the loops whose attribute
name is itself a variable, which are precisely the ones taking an arbitrary map.

Writing the guard found more than the review did: it named three sites, and the scan found
five more, in _partials/fixed-header.inc and grid/datagrid-grid.inc.
@blaipr
blaipr merged commit de03b04 into main Sep 17, 2026
8 checks passed
@blaipr
blaipr deleted the fix/a-data-attribute-is-escaped-like-everything-else branch September 17, 2026 02:11
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.

1 participant