Repository navigation
E4 · Block re-enablement across Blade, React, Vue renderers - #396
Conversation
… renderers
Promote the entity-scoped block set off the V1 deny-list now that B1's
expanded core-data shim and the C1–C5 backends can round-trip them, and
add Blade / React / Vue render implementations for each so server- and
client-rendered output stay in lockstep.
Newly exposed blocks (config + JS-mirror):
- core/template-part
- core/post-title, core/post-content, core/post-excerpt, core/post-date,
core/post-author, core/post-featured-image
- core/site-title, core/site-tagline, core/site-logo
- core/navigation (+ core/navigation-link, core/navigation-submenu
renderers)
Renderers read the resolved post / site / author payload from a
`_resolved*` attribute envelope a host-side resolver stamps onto the
block tree, mirroring the template-part inliner pattern. When a value
is missing the renderer emits a Gutenberg-shaped empty shell so the
front-end markup matches the editor preview even before a resolver is
wired up.
Shim stubs added so the newly-enabled edit components don't crash
against the empty-state core-data shim:
- `getCurrentTheme()` returns a synthetic `artisanpack-base` theme
record (matching `config('artisanpack.visual-editor.global_styles.
theme')`) so the template-part `enhanceTemplatePartVariations`
filter and placeholder code can read `?.stylesheet` /
`?.is_block_theme` without throwing.
- `getThemeSupports()` returns `{}`.
- `getPostType()` returns null.
- `getNavigationFallbackId()` is exposed as a public selector so
block-library's `unlock( useSelect( coreStore ) )` falls through to
it (returns `undefined` to opt the navigation block into its
uncontrolled-inner-blocks fallback).
Scope notes:
- Loop / feed widgets (core/query, core/latest-comments, etc.) stay
disabled — they need a real loop runtime + term/comment endpoints
that the shim does not implement (V2 + V1.1+).
- Inspector sidebar registry-isolation bug and core-data shim
auto-fetch gap surfaced during manual testing but predate E4 and are
filed as #394 and #395 respectively.
Closes #381
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughRe-enables entity-scoped core blocks and implements renderers for navigation, post-context, and site-context blocks across Blade, React, and Vue. Updates the core-data shim and visual-editor configuration. Adds per-renderer tests and registry entries; expands unit tests and snapshots to reflect the newly enabled blocks. Changes
Sequence Diagram(s)(No sequence diagrams generated.) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php (1)
226-271:⚠️ Potential issue | 🟡 MinorAdd Blade parity test for
javascript:navigation URLs.Navigation sanitization is asserted in React/Vue, but Blade currently lacks the negative-path assertion. Add it to keep renderer parity and catch regressions.
Suggested test addition
it( 'forces noopener when a navigation link opens in a new tab', function () { @@ expect( $rendered )->toContain( 'rel="noopener noreferrer"' ); } ); + +it( 'drops javascript: navigation link URLs', function () { + $tree = [ + blockNode( 'core/navigation', [], [ + blockNode( 'core/navigation-link', [ + 'label' => 'Bad', + 'url' => 'javascript:alert(1)', + ], [], 'nl-1' ), + ] ), + ]; + + $rendered = $this->stripGlobalStyles( renderTree( $tree ) ); + + expect( $rendered )->not()->toContain( 'javascript:' ); + expect( $rendered )->not()->toContain( 'href=' ); +} );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php` around lines 226 - 271, Add a Blade parity test to PostAndSiteContextRenderingTest that asserts navigation links with "javascript:" URLs are sanitized (i.e., not rendered as clickable hrefs) similar to React/Vue tests: create a new it( 'does not render javascript: navigation URLs', ... ) case using blockNode to build a core/navigation containing a core/navigation-link with 'url' => 'javascript:alert(1)', render it via renderTree and stripGlobalStyles, and assert the rendered output does NOT contain 'href="javascript:alert(1)"' and instead either omits the href or escapes it appropriately; locate where other navigation tests live (the existing it( 'renders a navigation block with menu items' ) and it( 'forces noopener...' ) cases) and add the new test nearby using the same helpers (blockNode, renderTree, stripGlobalStyles) for parity.
🧹 Nitpick comments (3)
packages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.php (1)
24-24: Consider omitting redundanttarget="_self"on site-title links.Line 24 always prints
target, but_selfis default browser behavior. Emittingtargetonly for_blankkeeps markup tighter and reduces renderer drift risk.♻️ Suggested adjustment
-<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $siteUrl )<a href="{{ $siteUrl }}" target="{{ $linkTarget }}" rel="{{ '_blank' === $linkTarget ? 'noopener noreferrer' : 'home' }}">{{ $title }}</a>@else{{ $title }}@endif</{{ $tag }}> +<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}"> +@if ( $isLink && '' !== $siteUrl ) + <a href="{{ $siteUrl }}"@if ( '_blank' === $linkTarget ) target="_blank" rel="noopener noreferrer"@else rel="home"@endif>{{ $title }}</a> +@else + {{ $title }} +@endif +</{{ $tag }}>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.php` at line 24, The template always emits target="{{ $linkTarget }}" for the site-title link even when $linkTarget is "_self"; change the markup in the <{{ $tag }}> block that builds the anchor (the code using $isLink, $siteUrl, $linkTarget, $title) to only render a target attribute when $linkTarget === '_blank' and only render rel="noopener noreferrer" in that same branch (otherwise omit target and rel entirely) so default browser behavior (_self) isn't redundantly output.packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts (1)
2-2: Initialize registry inbeforeEachto remove first-test state coupling.Line 21 only re-initializes after a test, so the first case depends on Line 4 side effects. Prefer explicit per-test setup.
Proposed test-lifecycle adjustment
-import { afterEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; @@ -afterEach(() => { +beforeEach(() => { resetBlockRegistry(); registerCoreBlocks(); }); + +afterEach(() => { + resetBlockRegistry(); +});Also applies to: 21-24
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts` at line 2, The registry used in PostAndSiteContextRendering.test.ts is only being reset in afterEach causing the first test to depend on module-level initialization; add an explicit beforeEach that initializes or reassigns the shared registry (the same variable currently reset in afterEach) so each test starts with a fresh registry instance, and keep or retain the existing afterEach cleanup if needed; update references in the describe block and tests that consume the registry to use this per-test initialization.packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx (1)
2-2: Prefer explicitbeforeEachregistration for deterministic setup.Line 18 initializes state only after tests. Seed registry in
beforeEachso the first test doesn’t rely on import-time state.Proposed lifecycle change
-import { afterEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; @@ -afterEach(() => { +beforeEach(() => { resetBlockRegistry(); registerCoreBlocks(); }); + +afterEach(() => { + resetBlockRegistry(); +});Also applies to: 18-21
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx` at line 2, Tests rely on import-time state seeding so make setup deterministic by moving registry seeding into an explicit beforeEach: add a beforeEach hook that calls the existing registry seed function (the same code currently executed at import/after tests) to initialize state before every test, keep afterEach for cleanup/unregistering, and update any references to the seed call (e.g., seedRegistry or registerTestData) so the first test no longer depends on import-time side effects; ensure beforeEach runs before tests by placing it alongside the existing afterEach in PostAndSiteContextRendering.test.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@config/visual-editor.php`:
- Around line 87-119: Update the stale explanatory paragraph earlier in this
file so it describes the E4 reality: mention that blocks like
'core/template-part', 'core/post-*', and 'core/site-*' are now promoted to the
allowed list (as shown in the allow-list array) and that 'core/navigation'
remains enabled, while deferred items such as 'core/query' remain in the
'disabled_blocks' array pending runtime/endpoint support; make the prose
consistent with the comments immediately above the 'disabled_blocks' array and
note that the JS mirror in site-editor-app.tsx must be kept in sync.
In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.php`:
- Line 42: The post title is being rendered as raw HTML using {!! $title !!},
creating a stored-XSS risk if upstream _resolvedTitle is unsanitized; update the
template to output an escaped title instead (use Blade's escaped output for the
$title variable) while keeping existing link logic ($isLink, $permalink,
$linkAttrs, $tag, $classes) intact so markup and attributes remain unchanged.
In `@packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx`:
- Line 66: Several places in postContext.tsx render raw HTML into the DOM (e.g.,
the anchor using linkProps and title: the dangerouslySetInnerHTML={{ __html:
title }} sink) without sanitization; replace direct injection by importing a
sanitizer (DOMPurify or equivalent) and run every `_resolved*`-derived value
through it before passing to dangerouslySetInnerHTML (e.g., sanitize title, plus
the other `_resolved*` values used at the other sinks on this file) so the
rendered payload is DOMPurify.sanitize(value) (or your project's sanitizer API)
and update all occurrences in postContext.tsx to use the sanitized string.
In `@packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts`:
- Around line 66-70: The renderer currently injects _resolved* HTML fields into
the DOM via innerHTML in postContext.ts (see the h(..., { innerHTML: ... })
usages) while attrString() only coerces types and does not escape HTML; either
ensure the upstream host-side resolver that produces these _resolved* properties
performs robust HTML sanitization (e.g., DOMPurify or equivalent) before
stamping them onto the block tree, or perform escaping/sanitization at this
boundary inside postContext.ts before assigning to innerHTML (replace or
sanitize the _resolved* values returned by the resolver); update or add tests
asserting that _resolved* properties passed to render functions are sanitized
and reference the attrString(), the renderer's innerHTML assignments, and the
host-side resolver API when making the change.
---
Outside diff comments:
In
`@packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php`:
- Around line 226-271: Add a Blade parity test to
PostAndSiteContextRenderingTest that asserts navigation links with "javascript:"
URLs are sanitized (i.e., not rendered as clickable hrefs) similar to React/Vue
tests: create a new it( 'does not render javascript: navigation URLs', ... )
case using blockNode to build a core/navigation containing a
core/navigation-link with 'url' => 'javascript:alert(1)', render it via
renderTree and stripGlobalStyles, and assert the rendered output does NOT
contain 'href="javascript:alert(1)"' and instead either omits the href or
escapes it appropriately; locate where other navigation tests live (the existing
it( 'renders a navigation block with menu items' ) and it( 'forces noopener...'
) cases) and add the new test nearby using the same helpers (blockNode,
renderTree, stripGlobalStyles) for parity.
---
Nitpick comments:
In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.php`:
- Line 24: The template always emits target="{{ $linkTarget }}" for the
site-title link even when $linkTarget is "_self"; change the markup in the <{{
$tag }}> block that builds the anchor (the code using $isLink, $siteUrl,
$linkTarget, $title) to only render a target attribute when $linkTarget ===
'_blank' and only render rel="noopener noreferrer" in that same branch
(otherwise omit target and rel entirely) so default browser behavior (_self)
isn't redundantly output.
In
`@packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx`:
- Line 2: Tests rely on import-time state seeding so make setup deterministic by
moving registry seeding into an explicit beforeEach: add a beforeEach hook that
calls the existing registry seed function (the same code currently executed at
import/after tests) to initialize state before every test, keep afterEach for
cleanup/unregistering, and update any references to the seed call (e.g.,
seedRegistry or registerTestData) so the first test no longer depends on
import-time side effects; ensure beforeEach runs before tests by placing it
alongside the existing afterEach in PostAndSiteContextRendering.test.tsx.
In
`@packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts`:
- Line 2: The registry used in PostAndSiteContextRendering.test.ts is only being
reset in afterEach causing the first test to depend on module-level
initialization; add an explicit beforeEach that initializes or reassigns the
shared registry (the same variable currently reset in afterEach) so each test
starts with a fresh registry instance, and keep or retain the existing afterEach
cleanup if needed; update references in the describe block and tests that
consume the registry to use this per-test initialization.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4b4038a4-b43a-47f7-a0dc-9a0f72629536
📒 Files selected for processing (30)
config/visual-editor.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/navigation-link.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/navigation-submenu.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/navigation.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/post-author.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/post-content.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/post-date.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/post-excerpt.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/post-featured-image.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/site-logo.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/site-tagline.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.phppackages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.phppackages/visual-editor-renderer-react/src/blocks/core/navigation.tsxpackages/visual-editor-renderer-react/src/blocks/core/postContext.tsxpackages/visual-editor-renderer-react/src/blocks/core/siteContext.tsxpackages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.tspackages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsxpackages/visual-editor-renderer-react/tests/registry.test.tspackages/visual-editor-renderer-vue/src/blocks/core/navigation.tspackages/visual-editor-renderer-vue/src/blocks/core/postContext.tspackages/visual-editor-renderer-vue/src/blocks/core/siteContext.tspackages/visual-editor-renderer-vue/src/blocks/registerCoreBlocks.tspackages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.tspackages/visual-editor-renderer-vue/tests/registry.test.tsresources/js/visual-editor/site-editor/site-editor-app.tsxresources/js/visual-editor/vendor/__tests__/core-data-shim.test.tsxresources/js/visual-editor/vendor/core-data-shim.tstests/Unit/VisualEditor/EnabledBlockNamesTest.php
| } | ||
| } | ||
| @endphp | ||
| <{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{!! $title !!}</a>@else{!! $title !!}@endif</{{ $tag }}> |
There was a problem hiding this comment.
Escape post title output instead of rendering raw HTML.
Line 42 outputs $title with {!! $title !!}. That creates a stored-XSS path if _resolvedTitle is ever unsanitized upstream. Use escaped output for title text.
🔧 Proposed fix
-<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{!! $title !!}</a>@else{!! $title !!}@endif</{{ $tag }}>
+<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{{ $title }}</a>@else{{ $title }}@endif</{{ $tag }}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{!! $title !!}</a>@else{!! $title !!}@endif</{{ $tag }}> | |
| <{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{{ $title }}</a>@else{{ $title }}@endif</{{ $tag }}> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.php`
at line 42, The post title is being rendered as raw HTML using {!! $title !!},
creating a stored-XSS risk if upstream _resolvedTitle is unsanitized; update the
template to output an escaped title instead (use Blade's escaped output for the
$title variable) while keeping existing link logic ($isLink, $permalink,
$linkAttrs, $tag, $classes) intact so markup and attributes remain unchanged.
|
|
||
| return ( | ||
| <Tag className={classes}> | ||
| <a {...linkProps} dangerouslySetInnerHTML={{ __html: title }} /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Where resolved HTML payloads are produced/assigned
rg -n --type=ts --type=tsx --type=php '_resolved(Content|Excerpt|Title|AuthorBio)'
# Existing sanitizer usage and dangerous sinks
rg -n --type=ts --type=tsx --type=php 'dangerouslySetInnerHTML|DOMPurify|sanitize|safeHtml|wp_kses|kses'Repository: ArtisanPack-UI/visual-editor
Length of output: 100
🏁 Script executed:
# Find and read the target file
fd -t f "postContext.tsx" 2>/dev/null | head -5Repository: ArtisanPack-UI/visual-editor
Length of output: 142
🏁 Script executed:
# Get the file size first
wc -l packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx 2>/dev/null || echo "File not found"Repository: ArtisanPack-UI/visual-editor
Length of output: 146
🏁 Script executed:
# Search for _resolved patterns without type restrictions
rg -n '_resolved(Content|Excerpt|Title|AuthorBio)' --max-count=50 2>/dev/null || trueRepository: ArtisanPack-UI/visual-editor
Length of output: 4259
🏁 Script executed:
# Search for dangerouslySetInnerHTML and sanitization patterns
rg -n 'dangerouslySetInnerHTML|DOMPurify|sanitize|safeHtml' --max-count=50 2>/dev/null || trueRepository: ArtisanPack-UI/visual-editor
Length of output: 8817
🏁 Script executed:
cat -n packages/visual-editor-renderer-react/src/blocks/core/postContext.tsxRepository: ArtisanPack-UI/visual-editor
Length of output: 11542
🏁 Script executed:
cat packages/visual-editor-renderer-react/package.json | grep -A 20 '"dependencies"'Repository: ArtisanPack-UI/visual-editor
Length of output: 54
🏁 Script executed:
cat packages/visual-editor-renderer-react/package.jsonRepository: ArtisanPack-UI/visual-editor
Length of output: 890
🏁 Script executed:
cat packages/visual-editor-renderer-react/src/support/attributes.tsRepository: ArtisanPack-UI/visual-editor
Length of output: 2690
🏁 Script executed:
fd -type f "Resolver" --max-depth 5 | head -10Repository: ArtisanPack-UI/visual-editor
Length of output: 243
🏁 Script executed:
rg -l "resolvedTitle|resolvedContent" --type=ts --type=tsx --max-count=20 2>/dev/null || trueRepository: ArtisanPack-UI/visual-editor
Length of output: 54
🏁 Script executed:
# Check the Blade templates to see if they sanitize
cat packages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.phpRepository: ArtisanPack-UI/visual-editor
Length of output: 1998
🏁 Script executed:
# Check the Vue templates to see how they handle it
rg -A 5 "_resolvedTitle|_resolvedContent" packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts | head -40Repository: ArtisanPack-UI/visual-editor
Length of output: 556
🏁 Script executed:
# Find where _resolved* attributes are set
rg -n "_resolvedTitle\s*=" --max-count=20 2>/dev/null || trueRepository: ArtisanPack-UI/visual-editor
Length of output: 54
🏁 Script executed:
# Look for where these resolved attributes are created
rg -B 3 -A 3 "attributes\._resolved" --max-count=30 2>/dev/null | head -50Repository: ArtisanPack-UI/visual-editor
Length of output: 5157
🏁 Script executed:
# Check for any sanitization in the main codebase
rg -n "wp_kses|sanitize.*html|DOMPurify" --max-count=20 2>/dev/null || trueRepository: ArtisanPack-UI/visual-editor
Length of output: 54
🏁 Script executed:
# Search for resolver or data processing that might sanitize
fd -name "*esolver*" 2>/dev/null | head -10Repository: ArtisanPack-UI/visual-editor
Length of output: 54
Add HTML sanitization for all dangerouslySetInnerHTML sinks using _resolved* values.
Lines 66, 71, 86, 123, and 205 inject HTML directly from _resolved* attributes without sanitization. These values are injected at the renderer boundary after attributes are populated upstream. Without explicit sanitization, any unsanitized content flowing through the resolver becomes an XSS vector.
Add DOMPurify (or equivalent) and sanitize these payloads before render:
Sanitization approach
+import DOMPurify from 'dompurify';
+
+function sanitizeHtml(html: string): string {
+ return DOMPurify.sanitize(html, { ALLOWED_TAGS: [] });
+}
-const title = attrString(attributes._resolvedTitle);
+const title = sanitizeHtml(attrString(attributes._resolvedTitle));
-const content = attrString(attributes._resolvedContent);
+const content = sanitizeHtml(attrString(attributes._resolvedContent));
-const excerpt = attrString(attributes._resolvedExcerpt);
+const excerpt = sanitizeHtml(attrString(attributes._resolvedExcerpt));
-const bio = attrString(attributes._resolvedAuthorBio);
+const bio = sanitizeHtml(attrString(attributes._resolvedAuthorBio));Alternatively, document and enforce a strict trusted-HTML contract at the resolver boundary (e.g., resolver must sanitize output before stamping _resolved* attributes).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx` at
line 66, Several places in postContext.tsx render raw HTML into the DOM (e.g.,
the anchor using linkProps and title: the dangerouslySetInnerHTML={{ __html:
title }} sink) without sanitization; replace direct injection by importing a
sanitizer (DOMPurify or equivalent) and run every `_resolved*`-derived value
through it before passing to dangerouslySetInnerHTML (e.g., sanitize title, plus
the other `_resolved*` values used at the other sinks on this file) so the
rendered payload is DOMPurify.sanitize(value) (or your project's sanitizer API)
and update all occurrences in postContext.tsx to use the sanitized string.
| return h(tag, { class: classes }, [h('a', { ...linkProps, innerHTML: title })]); | ||
| } | ||
|
|
||
| return h(tag, { class: classes, innerHTML: title }); | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Locate where resolved payload fields are produced.
rg -n --type=ts --type=tsx --type=js --type=jsx --type=php \
"_resolvedTitle|_resolvedContent|_resolvedExcerpt|_resolvedAuthorBio|_resolvedDateFormatted|_resolvedPermalink"
# 2) Inspect sanitization/escaping patterns in those producer areas.
rg -n -C3 --type=ts --type=tsx --type=js --type=jsx --type=php \
"sanitize|saniti[sz]e|escape|esc_html|esc_attr|wp_kses|DOMPurify|safeHtml|xss"
# Expected: `_resolved*` HTML-producing paths sanitize/escape once before renderer consumption.Repository: ArtisanPack-UI/visual-editor
Length of output: 100
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# First, check if the file exists and get its size
echo "=== Checking postContext.ts file ==="
find . -name "postContext.ts" -type f
# Check available file types for rg
echo -e "\n=== Searching for _resolved* patterns (correct file types) ==="
rg -n "_resolvedTitle|_resolvedContent|_resolvedExcerpt|_resolvedAuthorBio|_resolvedDateFormatted|_resolvedPermalink" \
--type ts --type js \
|| echo "No matches found"
# Look for sanitization patterns across the codebase
echo -e "\n=== Searching for sanitization patterns ==="
rg -n "sanitize|escape|DOMPurify|safeHtml|xss" \
--type ts --type js --type vue \
| head -30Repository: ArtisanPack-UI/visual-editor
Length of output: 5851
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the specific file to understand the context
echo "=== Reading postContext.ts ==="
file_path=$(find . -name "postContext.ts" -type f)
if [ -n "$file_path" ]; then
wc -l "$file_path"
echo -e "\n=== Full content of postContext.ts ==="
cat -n "$file_path"
else
echo "File not found"
fiRepository: ArtisanPack-UI/visual-editor
Length of output: 14130
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find and read the attributes support file
echo "=== Finding attributes support file ==="
find . -path "*/support/attributes*" -type f
# Read the attributes file to see attrString implementation
echo -e "\n=== Reading attributes file ==="
file_path=$(find . -path "*/support/attributes*" -type f | head -1)
if [ -n "$file_path" ]; then
echo "File: $file_path"
wc -l "$file_path"
echo ""
cat -n "$file_path"
else
echo "Attributes file not found"
fiRepository: ArtisanPack-UI/visual-editor
Length of output: 3757
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the urlSanitizer implementation to understand sanitization approach
echo "=== Finding urlSanitizer file ==="
find . -path "*/support/urlSanitizer*" -type f
# Read the urlSanitizer file
echo -e "\n=== Reading urlSanitizer file ==="
file_path=$(find . -path "*/support/urlSanitizer*" -type f | head -1)
if [ -n "$file_path" ]; then
echo "File: $file_path"
wc -l "$file_path"
echo ""
cat -n "$file_path"
else
echo "urlSanitizer file not found"
fiRepository: ArtisanPack-UI/visual-editor
Length of output: 1588
Verify that _resolved* HTML fields are sanitized at the resolver boundary.
Lines 66, 69, 90, 131, and 252 render _resolved* HTML values via innerHTML bindings. The attrString() function applies no HTML escaping—it only performs type coercion. The security responsibility is delegated to the upstream "host-side resolver" that stamps these values onto the block tree. Confirm that this resolver applies HTML sanitization (e.g., wp_kses, DOMPurify equivalent) before these attributes reach the renderer, or implement HTML escaping here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts` around
lines 66 - 70, The renderer currently injects _resolved* HTML fields into the
DOM via innerHTML in postContext.ts (see the h(..., { innerHTML: ... }) usages)
while attrString() only coerces types and does not escape HTML; either ensure
the upstream host-side resolver that produces these _resolved* properties
performs robust HTML sanitization (e.g., DOMPurify or equivalent) before
stamping them onto the block tree, or perform escaping/sanitization at this
boundary inside postContext.ts before assigning to innerHTML (replace or
sanitize the _resolved* values returned by the resolver); update or add tests
asserting that _resolved* properties passed to render functions are sanitized
and reference the attrString(), the renderer's innerHTML assignments, and the
host-side resolver API when making the change.
The block-registry filter docblock above `enabled_blocks` still described the pre-E4 state where every entity-scoped block was disabled. Update it to reflect the V1 reality (E4 promoted `core/template-part`, `core/post-*`, `core/site-*`, and `core/navigation`) and call out which blocks remain deferred to V2 / V1.1+, plus a cross-reference to the JS-mirror in `site-editor-app.tsx` that needs to stay in sync with the deny-list.
Description
Promote the entity-scoped block set off the V1 deny-list now that B1's expanded
core-datashim and the C1–C5 backends can round-trip them, and add Blade / React / Vue render implementations for each so server- and client-rendered output stay in lockstep.Closes: #381
Type of Change
Related Issue
Issue: #381
Motivation and Context
core-datastarted as a stub that disabled 17 blocks needing entity resolution. B1 expanded the shim, C1–C5 landed the backends. E4 is where the visible payoff lands — the editor's full block palette becomes usable.Changes Made
Blocks promoted to the V1 allow-list (config/visual-editor.php + the JS-mirror in
site-editor-app.tsx):core/template-partcore/post-title,core/post-content,core/post-excerpt,core/post-date,core/post-author,core/post-featured-imagecore/site-title,core/site-tagline,core/site-logocore/navigation(+core/navigation-link,core/navigation-submenurenderers)Renderers added in all three packages. Each block reads the resolved post / site / author payload from a
_resolved*attribute envelope a host-side resolver stamps onto the block tree (mirroring the template-part inliner pattern). When a value is missing the renderer emits a Gutenberg-shaped empty shell so the front-end markup matches the editor preview even before a resolver is wired up.Shim selectors added so the newly-enabled
Editcomponents don't crash against the empty-state core-data shim:getCurrentTheme()returns a syntheticartisanpack-basetheme record (matchesconfig('artisanpack.visual-editor.global_styles.theme')) so the template-partenhanceTemplatePartVariationsfilter and placeholder code can read?.stylesheet/?.is_block_themewithout throwing.getThemeSupports()returns{}.getPostType()returnsnull.getNavigationFallbackId()is exposed as a public selector so block-library'sunlock( useSelect( coreStore ) )falls through to it (returnsundefinedto opt the navigation block into its uncontrolled-inner-blocks fallback).Out of scope. Loop / feed widgets (
core/query,core/latest-comments,core/archives,core/categories,core/tag-cloud) stay disabled — they need a real loop runtime + term/comment endpoints that the shim does not implement (V2 + V1.1+).How Has This Been Tested?
Testing Environment:
release/1.0HEADTests Performed:
release/1.0— final pass returned no findings (resolved 4 XSS-via-relconcatenation findings + 1 inverted-logic finding on the navigationis-responsiveclass along the way).Tests Added
Test details:
PostAndSiteContextRenderingTest.php) covering all 11 newly-enabled blocks plus the navigation menu / submenu rendering matrix and URL-sanitisation safeguards on_resolvedPermalink.PostAndSiteContextRendering.test.{tsx,ts}) — 18 tests each, mirroring the Blade matrix.registerCoreBlocks().EnabledBlockNamesTestsnapshot updated to reflect the new V1 allow-list ordering.getCurrentTheme(),getPostType(),getThemeSupports(), andgetNavigationFallbackId().Accessibility Tests Run
Details:
The renderer output preserves Gutenberg core's wrapper semantics (correct heading levels for post/site title via
levelclamp,<time datetime>on post-date,<figure>on featured image,<nav aria-label>on navigation,<ul>menu containers for navigation children). No new interactive surfaces are introduced — all blocks render to static HTML. Tests assertaria-labelpropagation on the navigation block.Documentation
Documentation details:
Each new renderer file carries a top-of-file docblock explaining the
_resolved*attribute envelope + parity goal with the other renderers. Inline comments in the shim explain why each new selector exists and what it returns. README / wiki updates can land alongside the resolver work that fills in the_resolved*attribute envelope (filed separately).Screenshots
Manual browser testing was performed in the site editor; no screenshot panel needed since the visible output is "blocks no longer crash on insert" and the rendered markup matches Gutenberg's wrapper semantics.
Pre-Submission Checklist
Follow-up issues opened during testing
Two pre-existing issues surfaced while exercising the new blocks. Filed separately so this PR can land without bundling shim or shell work:
core-datashim'suseEntityRecords/getEntityRecordsshould trigger REST resolutionSummary by CodeRabbit
New Features
Tests