Pattern directory: apply the editor block allowlist during validation - #765
Merged
Conversation
The editor's `allowed_block_types_all` filter already hides `wporg/*` and a handful of core blocks from the pattern inserter, but the REST content validator only checked that each block was registered. Share that policy between the two via a single `is_block_allowed_in_pattern()` predicate so a submission can't include a block type the editor never offers, and reject any that slip through with a `rest_pattern_disallowed_blocks` error. Adds coverage for the shared predicate and updates the content-validation tests accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two defense-in-depth measures alongside the block allowlist: - Reject Interactivity API `data-wp-*` directives in submitted block HTML. Core blocks emit these at render time and never store them, so their presence in a submission is markup trying to drive a store; KSES keeps them and they live in inner HTML, out of reach of the attribute check. - Guard the `pattern_content` REST field so it only returns content for an actual pattern, returning empty when the id resolves to another post type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
obenland
force-pushed
the
pattern-directory/server-block-allowlist
branch
from
August 31, 2026 14:20
487cbbd to
0f9e39d
Compare
Follow-ups from review of the allowlist and directive validators: - Validate the exact content that will be stored. Stripping `\n\n` before parsing let a delimiter like `<!-- wp:shortcode\n\n-->` vanish from the validator's view while still parsing as a real block once saved. Skip the editor's whitespace-only separator "blocks" during flattening instead. - Disallow `core/pattern`, which splices another registered pattern in by slug on render - the same indirection `core/block` and `core/template-part` are blocked for. - Run the block allowlist and directive checks on the translation import too: translator-supplied strings are assembled into stored markup without passing through the REST validators. The cron now logs a refused import. - Extract `content_has_block_directives()` so both paths share the scan, and skip tokenizing entirely when no `data-wp-` marker is present. - Cast the block name once in `is_block_allowed_in_pattern()` so the prefix test and the strict `in_array()` judge the same value. - Test hygiene: unregister the test block in a `finally`, dedupe the paragraph fixture, and cover the two new rejections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ract A translation refused by the content guard only surfaced in echoed cron output, which isn't reliably captured, so also send it to the server log with the pattern name and locale. Document that `create_or_update_translated_pattern()` can return a `WP_Error`, and correct the guard's docblock to state which checks it actually runs rather than claiming full parity with the REST validators. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Pattern Directory editor already keeps
wporg/*blocks and a small set of core blocks out of the inserter via theallowed_block_types_allfilter, but the REST content validator only checked that each submitted block was registered — so those same block types were still accepted on the server (its error message even promises "core blocks" only). This makes the server enforce the same policy as the editor, from one shared place, and closes a few adjacent gaps found in review.Changes
Block allowlist (server-side)
wporg/*rule into a sharedDISALLOWED_BLOCK_TYPESconstant andis_block_allowed_in_pattern()predicate.remove_disallowed_blocks()(the editor filter) now uses the predicate instead of its own inline list.validate_content()runs every submitted block — flattened, so nested blocks are covered — through the same predicate and returns arest_pattern_disallowed_blockserror for anything the editor wouldn't offer.core/patternto the disallowed set: it splices another registered pattern in by slug on render, the same indirectioncore/blockandcore/template-partare already blocked for.Validate the exact stored content
\n\nout of the content before parsing. That normalisation could hide a block from the validator (e.g. a<!-- wp:shortcode\n\n-->delimiter) while it still parsed as a real block once stored. Whitespace-only separator "blocks" are skipped during flattening instead.Interactivity directives
data-wp-*directives carried in a block's inner HTML (rest_pattern_interactivity_directive). KSES preserves them and they sit in inner HTML, out of reach of the block-attribute check. Shared ascontent_has_block_directives(), which skips tokenising entirely when nodata-wp-marker is present.Translation import path
create_or_update_translated_pattern()returns aWP_Errorwhen content is refused, and the cron logs the refusal (pattern name + locale) to the server log.pattern_contentREST fieldwporg-pattern, returning empty when the id resolves to a different post type.Tests
test_wporg_blocks_are_disallowedcovering thewporg/*path, plus data-provider cases forcore/nextpage,core/shortcode(flat and nested),core/pattern, the\n\n-in-delimiter bypass, and interactivity directives (data-wp-interactive/data-wp-init,data-wp-bind--src). The existingcore/nextpage"valid" case moved to "disallowed" to match the editor.phpcsclean.