Skip to content

Improve handling of content and profiles from remote servers - #3703

Merged
pfefferle merged 24 commits into
trunkfrom
fix/remote-content-sanitization
Sep 1, 2026
Merged

Improve handling of content and profiles from remote servers#3703
pfefferle merged 24 commits into
trunkfrom
fix/remote-content-sanitization

Conversation

@pfefferle

Copy link
Copy Markdown
Member

Proposed changes:

This hardens how the plugin handles content and profile fields that come from remote servers. Titles, summaries, captions, display names, post and comment bodies are now cleaned on the way in, once, at the point where they are stored, so what is saved is already safe for the sinks that later render it. Plain-text fields are treated as plain text, rich fields keep a narrow allowlist that drops inline styles, and the same treatment is applied to the Mastodon importer and the Social Web reader.

A few fields were reaching an output sink under a wider policy than the one guarding the inbox. Those are routed through the same cleaning as everything else, so there is one policy per field rather than two.

  • Remote titles, captions and display names are stored and rendered as plain text. Characters like a bare less-than sign or a percent sequence survive instead of truncating the value.
  • Rich remote content keeps a post-style allowlist without inline styles.
  • The reader renders stored values as-is and does not decode them again, and the reactions endpoint returns author names as plain text.
  • The Mastodon importer cleans the same fields the same way live federation does, and no longer creates duplicate posts when an archive is imported twice.

Other information:

  • Have you written new tests for your changes, if applicable?

Testing instructions:

  • Follow a few remote accounts and view their posts, profiles and replies in the reader and on the front end. Content should render as expected, with no leftover markup or missing characters.
  • Import a Mastodon archive, then import it again. The second run should not duplicate posts.
  • Set media-library alt text containing a less-than sign, federate the attachment, and confirm the value is preserved.

Changelog entry

The entries are already part of the branch.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the WordPress ActivityPub plugin’s handling of remote-authored fields by sanitizing content at ingest (before storage) and ensuring the reader/UI renders stored values without a second decoding/unescaping pass that could re-activate inert text.

Changes:

  • Add/standardize sanitization utilities for remote plain-text fields and remote HTML (posts vs comments), including removing inline styles and HTML comments (block delimiters) before storage.
  • Apply the same ingest sanitization across federation paths, the Mastodon importer (including improved de-duplication), and reactions author-name rendering.
  • Expand PHPUnit + React/Jest coverage to pin security-relevant invariants (no entity-decoding before dangerouslySetInnerHTML, no style/block delimiter persistence, no duplicate imports).

Reviewed changes

Copilot reviewed 42 out of 43 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/phpunit/tests/includes/wp-admin/import/class-test-mastodon.php Adds importer regression tests for sanitization and de-duplication/threading behavior.
tests/phpunit/tests/includes/transformer/class-test-attachment.php Pins attachment alt-text behavior for bare < and entity decoding.
tests/phpunit/tests/includes/rest/class-test-post-controller.php Expands reactions endpoint tests to cover “bare less-than” author names.
tests/phpunit/tests/includes/collection/class-test-remote-posts.php Adds tests for excerpt/title sanitization and slash preservation.
tests/phpunit/tests/includes/collection/class-test-remote-actors.php Adds search regression test for escaped display names.
tests/phpunit/tests/includes/collection/class-test-interactions.php Adds tests ensuring comment sanitization is independent of current user and uses the comment allowlist for quotes.
tests/phpunit/tests/includes/class-test-sanitize.php Adds coverage for stripping inline styles, block delimiters, and deep-encoded payload safety.
tests/phpunit/tests/includes/class-test-functions-comment.php Adds tests for reaction-author name cleaning.
tests/phpunit/tests/includes/class-test-comment.php Adds tests ensuring emoji unescape is scoped to ActivityPub comments and filter wiring passes comment IDs.
tests/phpunit/tests/includes/class-test-attachments.php Adds tests ensuring attachment captions/alt/title are sanitized and preserve bare <.
templates/embed.php Routes embed content through remote-HTML sanitization.
templates/emails/new-follower.php Tightens email summary allowlist to user-description HTML plus <br>.
src/reactions/render.php Uses shared reaction-author-name helper for consistent plain-text output.
src/app/routes/feed/inspector.tsx Stops decoding/unescaping before dangerouslySetInnerHTML; renders plain-text title as a React child.
src/app/routes/feed/tests/inspector.test.tsx Adds tests to ensure entity-encoded payloads are not revived in inspector rendering.
src/app/README.md Documents the “never decode rendered fields before innerHTML” rule and why.
src/app/components/fields/title/index.tsx Removes backslash-unescaping; keeps entity decoding only for React-child rendering.
src/app/components/fields/title/tests/index.test.tsx Adds tests pinning safe text rendering for decoded titles.
src/app/components/fields/content/index.tsx Stops decoding before rendering stored HTML; avoids double-decoding when using stripHTML().
src/app/components/fields/content/tests/index.test.tsx Adds tests preventing entity-decoding regressions and verifying safe rendering.
includes/wp-admin/import/class-mastodon.php Sanitizes imported post/comment content on write; de-dupes via _source_id first and preserves reply threading.
includes/transformer/class-attachment.php Ensures attachment alt text is cleaned safely and decoded for plain-text JSON output.
includes/rest/class-post-controller.php Uses shared reaction-author-name helper in REST reactions output.
includes/functions-comment.php Adds shared get_reaction_author_name() helper for consistent plain-text reaction author handling.
includes/collection/class-remote-posts.php Sanitizes remote post title/summary as plain text, and ensures values are slashed appropriately for storage.
includes/collection/class-remote-actors.php Stores remote actor names as cleaned plain text and broadens search to match escaped names.
includes/collection/class-interactions.php Ensures remote comment content/author are sanitized independent of request kses state; unifies allowlist logic.
includes/class-sanitize.php Introduces remote-specific sanitization APIs (plain text, HTML, comment HTML) and removes style/comments to prevent bypass.
includes/class-comment.php Scopes emoji unescape to ActivityPub comments and ensures filter receives/derives the comment ID.
includes/class-attachments.php Cleans remote attachment “name” before storing title/content/alt to avoid unsafe HTML persistence.
build/reactions/render.php Generated build artifact updated (not reviewed).
build/app/routes/feed/content.asset.php Generated build artifact updated (not reviewed).
.github/changelog/fix-remote-plain-text-fields Changelog entry for remote plain-text field preservation.
.github/changelog/fix-remote-content-inline-styles Changelog entry for stripping inline styles from remote content.
.github/changelog/fix-remote-comment-sanitization Changelog entry for sanitizing remote replies imported as comments.
.github/changelog/fix-remote-block-delimiters Changelog entry for stripping remote block markup before save.
.github/changelog/fix-remote-backslashes Changelog entry for preserving backslashes from remote content.
.github/changelog/fix-reader-remote-content Changelog entry for rendering stored remote content as-is in the reader.
.github/changelog/fix-mastodon-import-duplicates Changelog entry for Mastodon importer de-duplication.
.github/changelog/fix-mastodon-import-comment-content Changelog entry for sanitizing imported Mastodon reply comments.
.github/changelog/fix-mastodon-import-attachment-name Changelog entry for sanitizing imported Mastodon media descriptions.
.github/changelog/fix-comment-author-emoji-scope Changelog entry for scoping comment-author emoji behavior to ActivityPub comments.
Suppressed comments (1)

includes/functions-comment.php:280

  • html_entity_decode() here should also specify encoding (and ENT_HTML5) for consistency with the decode loop and other usages.
	return \html_entity_decode( $author, ENT_QUOTES );

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread includes/functions-comment.php Outdated
Comment thread src/app/components/fields/title/__tests__/index.test.tsx Outdated
Comment thread includes/functions-comment.php Outdated
Comment thread includes/class-sanitize.php Outdated
Comment thread src/app/components/fields/content/__tests__/index.test.tsx
Comment thread src/app/routes/feed/__tests__/inspector.test.tsx
jeherve
jeherve previously approved these changes Sep 1, 2026

@jeherve jeherve left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good, should be ready to merge!

@pfefferle
pfefferle merged commit 4344a64 into trunk Sep 1, 2026
12 checks passed
@pfefferle
pfefferle deleted the fix/remote-content-sanitization branch September 1, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants