You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Remaining, not yet in a PR — Block Supports: typography.php:230 and block-style-variations.php:63 (see Target areas).
Scope note
Only fatal operations are listed. sprintf('%s'), str_replace, preg_replace, and "{$array}" interpolation only emit warnings (garbage CSS, not a crash) and are excluded.
Fatal-throwing sinks include preg_match/preg_quote/explode/trim/ltrim/rtrim/strlen/substr/strtolower/str_contains/str_starts_with/str_ends_with/parse_url/tag_escape/is_email/addcslashes/esc_url on an array, and array/array-typed values used as array offsets in isset()/array_key_exists()/[].
Correction:esc_attr(), esc_html(), and wp_strip_all_tags() are not fatal sinks. esc_attr/esc_html run the value through wp_check_invalid_utf8(), which does (string) $value — an array becomes the string "Array" with a warning, not a crash. wp_strip_all_tags() self-guards non-scalar input since WP 6.7 (returns '' with an E_USER_WARNING); Gutenberg requires WP 6.9. Only esc_url() among the escaping functions fatals on an array (via ltrim). An earlier version of this issue over-listed esc_attr/wp_strip_all_tags sites as fatal; they are excluded or dropped below.
Save / REST-time validation (the Global Styles: Reject non-string custom CSS in the REST controller #80338 idiom): for values persisted through a REST route (global styles, custom CSS), add a schema type and a validate_callback that returns WP_Error so bad data is rejected with a 400 and never stored. Preferred where a save boundary exists, because it stops the value at the door instead of guarding every downstream reader.
Target areas
Block supports:
lib/block-supports/layout.php:551-552 -> explode() on layout.contentSize / wideSize
lib/block-supports/layout.php:1450,1479 -> preg_quote() on attrs.tagName (Group)
lib/block-supports/layout.php:750,757,770,781 -> array_key_exists() with justifyContent/verticalAlignment as key
lib/block-supports/layout.php:409,1110,1114,1123 -> minimumColumnWidth / orientation / type
media-text/index.php:34, cover/index.php:137 -> focalPoint['x']/['y'] arithmetic (fatal when focalPoint is a string, or x/y is an array)
query/index.php:121,124,145 -> queryId used as an array key in $dirty_enhanced_queries[...] (only when enhancedPagination is true)
Excluded — not fatal on WP >= 6.9 (see Scope note), cosmetic only:
table-of-contents/index.php:27, tab-list/index.php:26, search/index.php:151 -> wp_strip_all_tags(ariaLabel/buttonText). The wrapper self-guards; guarding them only avoids an E_USER_WARNING / supplies a fallback label.
Proposed fix
Apply the established idiom already used in the PRs above and in sibling code (anchor.php:45; dimensions.php gutenberg_is_explicit_aspect_ratio_value): guard with is_string()/is_array()/is_scalar() (and/or an explicit cast) before each strict operation, treating a wrong-typed value as absent. Land as small, per-file PRs; all are core backport candidates.
Non-goal / related work
Generation-time validation for AI agents (e.g. a WordPress Abilities API style validator that hands agents the theme's constraints and checks their output before save) would reduce the rate of bad input, but does not remove the need for these guards: malformed content already exists in databases and will keep arriving from tools, imports, and hand-editing that don't validate.
Prevention and hardening are complementary. This issue covers hardening.
Summary
Some block supports and the block library consume block attributes and theme.json/global styles values as if their PHP types were guaranteed.
They are not!
block.jsontype/defaultand theme.json schema are not enforced at render time for hand-edited, imported, or AI-generated serialized content.A wrong-typed value survives deserialization and reaches a strict operation, producing a fatal
TypeErroron the front end.Reported in the wild (AI site generation is one known source):
This keeps happening, and we keep fixing it one crash at a time.
Recent reactive fixes, all merged (several backported to core):
gutenberg_sanitize_block_gap_value:blockGapnested array ({"top":["1rem"]}) →preg_match. Fixed withis_scalar+(string)cast. (core backport 7a7652e, Fix aria-checked attribute not set for plugin settings buttons in Options dropdown #65667)classNameas array ({"className":["0","1"]}) → string ops. Fixed withis_string( $block['attrs']['className'] ?? null ) ? ... : ''.gutenberg_render_elements_class_name:className→preg_match. Fixed with an earlyis_string()return.validate_custom_css:styles.cssnon-string →strlen. Fixed at the REST boundary (schema type +WP_Errorrest_custom_css_invalid_type, 400 instead of a 500 fatal).This issue proposes auditing and hardening the remaining sites proactively rather than waiting for each one to surface as a production fatal.
Progress
wp_strip_all_tagscases, see Scope note).typography.php:230andblock-style-variations.php:63(see Target areas).Scope note
Only fatal operations are listed.
sprintf('%s'),str_replace,preg_replace, and"{$array}"interpolation only emit warnings (garbage CSS, not a crash) and are excluded.Fatal-throwing sinks include
preg_match/preg_quote/explode/trim/ltrim/rtrim/strlen/substr/strtolower/str_contains/str_starts_with/str_ends_with/parse_url/tag_escape/is_email/addcslashes/esc_urlon an array, and array/array-typed values used as array offsets inisset()/array_key_exists()/[].Correction:
esc_attr(),esc_html(), andwp_strip_all_tags()are not fatal sinks.esc_attr/esc_htmlrun the value throughwp_check_invalid_utf8(), which does(string) $value— an array becomes the string"Array"with a warning, not a crash.wp_strip_all_tags()self-guards non-scalar input since WP 6.7 (returns''with anE_USER_WARNING); Gutenberg requires WP 6.9. Onlyesc_url()among the escaping functions fatals on an array (vialtrim). An earlier version of this issue over-listedesc_attr/wp_strip_all_tagssites as fatal; they are excluded or dropped below.Two complementary defense layers
typeand avalidate_callbackthat returnsWP_Errorso bad data is rejected with a 400 and never stored. Preferred where a save boundary exists, because it stops the value at the door instead of guarding every downstream reader.Target areas
Block supports:
ref; guard exists but the invalid-ref branch is missing acontinueBlock library:
separatorsmallestFontSizeurlExcluded — not fatal on WP >= 6.9 (see Scope note), cosmetic only:
E_USER_WARNING/ supplies a fallback label.Proposed fix
Apply the established idiom already used in the PRs above and in sibling code (anchor.php:45; dimensions.php
gutenberg_is_explicit_aspect_ratio_value): guard withis_string()/is_array()/is_scalar()(and/or an explicit cast) before each strict operation, treating a wrong-typed value as absent. Land as small, per-file PRs; all are core backport candidates.Non-goal / related work
Generation-time validation for AI agents (e.g. a WordPress Abilities API style validator that hands agents the theme's constraints and checks their output before save) would reduce the rate of bad input, but does not remove the need for these guards: malformed content already exists in databases and will keep arriving from tools, imports, and hand-editing that don't validate.
Prevention and hardening are complementary. This issue covers hardening.