AI Copilot: gate comment search on parent-post readability - #804
Merged
epeicher merged 4 commits intoSep 12, 2026
Merged
Conversation
The search-comments and search-comments-by-post abilities authorized on the comment's moderation status alone, so any logged-in user could read approved comments attached to private, draft, and password-protected posts, along with the private parent's title. Filter results by the caller's ability to read the parent post, mirroring Core's WP_REST_Comments_Controller::check_read_post_permission().
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Parent checks remain bypassable through custom post types and final entity hydration.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds parent-post authorization to AI comment search and regression coverage.
Changes:
- Filters comment results by parent readability.
- Rejects unreadable by-post searches.
- Adds subscriber/admin permission tests.
File summaries
| File | Description |
|---|---|
includes/ai-copilot/search.php |
Adds parent visibility gates. |
tests/phpunit/tests/aiNativeSearch.php |
Adds authorization regression tests. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Review follow-ups: a published post of a non-viewable post type no longer counts as readable (publish alone is not visibility, and read_post resolves to plain read for public statuses, so those parents now require edit_post); the final entity builder re-checks readability on the model-supplied id for both the comment and post branches, since model output is untrusted, and also honours the comment's moderation status. Adds regression tests for the orphaned-comment guard, the non-viewable CPT case, and entity hydration, and documents the caller-scoped filtering in the ability descriptions and the JS reference.
…ion-missing-authorization-in-the-copilot-comment
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
A valid post-password cookie bypasses the stated edit_post requirement.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
A caller holding a valid wp-postpass cookie has already entered the correct password, and post_password_required() honouring it is Core parity (the comments REST controller reads the same cookie), not a bypass. The docblock overclaimed that the password could not be supplied at all; correct it and pin the intended behaviour with a regression test so a future hardening pass does not lock out readers the author gave the password to.
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The authorization checks now cover search, by-post lookup, and untrusted entity hydration with focused regression tests and updated contracts.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Balanced
epeicher
enabled auto-merge (squash)
September 12, 2026 09:47
epeicher
deleted the
opensta-152-openstation-missing-authorization-in-the-copilot-comment
branch
September 12, 2026 09:55
epeicher
added a commit
that referenced
this pull request
Sep 12, 2026
…ion-missing-authorization-to-authenticated #804 landed the same class of fix in parallel, so the two sides had grown their own readability helper. Collapse onto trunk's: openstation_ai_can_read_post() is a superset of the one this branch added — it takes an id or an object, refuses id 0 rather than letting get_post() fall back to the global $post, and already gates the post type, which is what the last review round asked for here. Trunk also settled the shape of the comment-search gate, and it settled it against the one this branch had built. Filtering parent status and post type in the query made `total` exact, but a query var cannot say "readable by THIS caller": trunk pins an Administrator still finding comments on private posts, and a reader who entered a post password still reading that post's discussion. Both are right, and both are lost to a blanket publish-only filter. The per-row filter stays, the counter skew is now stated where it happens, and the agents-security checklist says which of the two shapes a new gate should reach for. What survives from this branch on top of trunk: the post/page pin on the entity card (a viewable CPT row passes the read gate and still has no card to land in), the moderation verdicts following `moderate_comments`, and the wp-admin edit link following `edit_comment`.
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.
A Subscriber can no longer read comments attached to private, draft, or password-protected posts through the Copilot's comment search abilities.
desktop-mode/search-commentsanddesktop-mode/search-comments-by-postauthorized on the comment's moderation status alone:get_comments()ran withstatus => approveand nothing about the parent post. Both abilities are read-only and REST-exposed, so Core dispatches them overGETto any logged-in user, who received the comment text and the private parent's title. Approval is a moderation decision about the comment; it has never meant the discussion is visible.Comment results are now gated on the parent post's readability, mirroring Core's
WP_REST_Comments_Controller::check_read_post_permission():search_commentsdrops every row whose parent the caller cannot read. A password-protected parent requires the password satisfied oredit_post(post_password_required()honours thewp-postpasscookie Core's password form sets after the correct password is entered, matching Core's comments controller; the ability itself has no password input), a publicly viewable parent passes, a parent of a non-viewable post type (an internal/admin-only CPT) requiresedit_post(for a public status,read_postresolves to plainread, so the capability alone cannot gate those), and any other parent requiresread_post.search_comments_by_postrefuses the whole request up front when the target post is not readable, returning an empty batch without the parent title, and with the same error shape for a nonexistent and an unreadable post.comment_post_IDof 0) are dropped rather than judged against the global$postthatget_post( 0 )falls back to.entityrecord re-checks readability on the model-supplied id, for the comment branch and the post/page branch. Model output is untrusted (a search turn can be driven by attacker-controlled content), so hydration cannot assume the id came out of a filtered tool result. Unapproved comments hydrate only for callers withedit_comment. Unreadable ids resolve tonull, indistinguishable from nonexistent ones.The gate is per-caller, not a blanket publish filter: an administrator still finds comments on private posts. The abilities GET path and the Copilot's own search loop run through the same fetch handlers, so both are covered at the source. As in Core's comments controller, rows are filtered after the query, so
totalstill counts all approved matches; the returned items and titles are what carry content. The ability descriptions anddocs/javascript-reference.mddocument the caller-scoped filtering and the pagination semantics.Validation
$post, and entity hydration refusing unreadable posts, hidden comments, and unapproved comments.WP_REST_Comments_Controlleras the reference behavior.phpcs -nclean on the touched source;npm run buildproduced no further diffs.