Skip to content

Close the comment read paths that bypass comments_pre_query - #48

Open
jeremyfelt wants to merge 2 commits into
trunkfrom
fix/close-comment-read-paths
Open

Close the comment read paths that bypass comments_pre_query#48
jeremyfelt wants to merge 2 commits into
trunkfrom
fix/close-comment-read-paths

Conversation

@jeremyfelt

@jeremyfelt jeremyfelt commented Aug 21, 2026

Copy link
Copy Markdown
Member

filter_comments_pre_query() only covers WP_Comment_Query. Comment feeds and the REST API each read comments another way, so historical approved comments were still served in full on a site running this plugin.

Reproduced on wp-env (WordPress 7.0, plugin active, one approved comment on post 1) before and after:

Request Before After
/wp-json/wp/v2/comments/3 200, full comment 404
/wp-json/wp/v2/comments 200, [] 404
POST /wp-json/wp/v2/comments 401 404
/?feed=comments-rss2 200, full comment 410
/?feed=comments-atom 200, full comment 410
/comments/feed/ 200 410
/hello-world/feed/ 200, full comment 410
/hello-world/feed/atom/ 200, full comment 410
/?p=1&feed=rss2&withcomments=1 200, full comment 410
/feed/, /feed/atom/ 200 200
/, /hello-world/, /?s=hello, /wp-sitemap.xml 200 200
/wp-json/, /wp-json/wp/v2/posts, /wp-json/wp/v2/posts/1 200 200

The "after" run was done with the site's cached rewrite_rules still holding all nine comment rules, including comments/feed. Nothing here depends on the rewrite rules being regenerated.

Changes

Block comment feed requests with a 410 response. WP_Query builds comment feeds with its own SQL (class-wp-query.php:2815 for archives, :3475 for singular), so comments_pre_query never runs. is_comment_feed() on template_redirect catches every variant, including the query-string form and per-post feeds. 410 rather than 404 tells feed readers the resource is permanently gone; wp_die() returns an XML error body when the request asks for XML.

Remove the core comment routes from the REST API. WP_REST_Comments_Controller resolves a single comment with get_comment(), which never runs a WP_Comment_Query. Filtering rest_endpoints drops /wp/v2/comments and /wp/v2/comments/<id>, which also takes them out of the /wp-json/ index and closes the write endpoints. Only the wp/v2 namespace is touched.

Checks

composer phpcs and composer phpstan (level 9) both clean. No PHP notices in debug.log.

Not included

  • Rewrite rule flushing on activation and deactivation. The comments_rewrite_rules filter only affects rules as they are generated, so a site keeps whatever is cached in rewrite_rules until someone re-saves the permalink settings. Left alone deliberately: those stale rules resolve to comment feeds that now return 410 regardless, so the only thing flushing buys is a tidier rule table, at the cost of activation/deactivation hooks this plugin does not otherwise need.
  • readme.txt / README.md description bullets and the changelog. This repo adds those in the release commit, not in fix commits.
  • The replies link in post REST responses. Core hard-codes it for post and page regardless of comment support (class-wp-rest-posts-controller.php:2258), so ?_embed now returns a rest_no_route error object there instead of comment data. No leak, just a dangling link.
  • XML-RPC wp.getComment, which also calls get_comment() directly. It requires an authenticated user with moderate_comments, so it is not part of the public exposure this fixes.

🤖 Generated with Claude Code

https://claude.ai/code/session_017SdvjbC3xVfdC4jVZNrChL

jeremyfelt and others added 2 commits August 21, 2026 10:42
WP_Query loads comment feeds with its own SQL instead of
WP_Comment_Query, so the comments_pre_query filter never sees them and
historical comments were served in full at /?feed=comments-rss2,
/comments/feed/, and any per-post /<post>/feed/ URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SdvjbC3xVfdC4jVZNrChL
WP_REST_Comments_Controller loads a single comment with get_comment(),
which bypasses WP_Comment_Query, so /wp/v2/comments/<id> served approved
comments in full even though the collection endpoint returned an empty
array. Dropping the routes closes the read and write endpoints together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SdvjbC3xVfdC4jVZNrChL
@github-actions

Copy link
Copy Markdown

Plugin Check: 1 error(s), 0 warning(s)

File Line:Col Type Code Message
0:0 ERROR outdated_tested_upto_header Tested up to: 7.0 < 7.1. The "Tested up to" value in your plugin is not set to the current version of WordPress. This means your plugin will not show up in searches, as we require plugins to be compatible and documented as tested up to the most recent version of WordPress.

@jeremyfelt
jeremyfelt force-pushed the fix/close-comment-read-paths branch from 4322262 to 5017e92 Compare August 21, 2026 17:56
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