Skip to content

bcc-login: close visibility leaks (REST, oEmbed, feeds, endpoints, SearchWP) and restore Phrase access - #227

Merged
cs1m0n merged 3 commits into
masterfrom
fix/bcc-login-visibility-leaks
Oct 13, 2026
Merged

cs1m0n merged 3 commits into
masterfrom
fix/bcc-login-visibility-leaks

Conversation

@cs1m0n

@cs1m0n cs1m0n commented Oct 13, 2026

Copy link
Copy Markdown
Member

Summary

Restores Phrase's access to restricted posts, which stopped in 1.2.8, and closes several ways restricted posts could be read without logging in: the REST API, oEmbed, Yoast SEO's head route, feeds, bcc-login's endpoints and SearchWP. The changes are in bcc-login's visibility, feed and endpoints classes; no settings or database changes.

Changes

Query filters

  • The the_posts backstop from 1.2.8 only re-checks queries the visibility filter set up, so the queries it exempts (Phrase, editors, feeds read with the key) stay exempt.
  • bcc-login's internal query vars are ignored when they come in with a query's own arguments.
  • Queries that another pre_get_posts callback switches to a supported post type are checked again.

SearchWP

  • The rules reach SearchWP as a plain post ID condition instead of a meta query, which skewed its ranking and slowed searches down. This covers its native search (also on CPT archives), live search, REST API and shortcodes.

REST API

  • Single posts from the posts controllers (posts, pages, media, CPTs) follow the rules of the post's own page, and are otherwise refused with core's rest_forbidden (401/403). Editors are exempt, so the block editor is unaffected.
  • On sites whose default visibility isn't public, REST lists and /wp/v2/search leave out post types without visibility support for visitors below the default.
  • /oembed/1.0/embed follows the same rules. Embeds of the site's own posts in content are decided per visitor when output, because the cached embed is shared by everyone.
  • Yoast SEO's /yoast/v1/get_head no longer returns the head (title, description) of a post the visitor may not view.
  • The old rest_pre_echo_response filter is removed. Its single-post check never matched these routes, and its search post-filter is replaced by filtering in the query (it also turned error responses into PHP fatals).

Feeds and endpoints

  • ?post_types= is sanitised and limited to viewable post types (reusable blocks and contact forms excluded). translatedFrom only points to a published original with the same audience.
  • bcc-login's endpoints are served at template_redirect, so they can't be combined with a feed. Only the known endpoints skip the login check, and HEAD requests don't run them.

Sites whose default visibility isn't public

  • Archives, search and core feeds are served when they were filtered for the visitor and hold posts they may see. Single views, unfiltered listings, comment feeds and other handlers fall back to the site default, as before.

Worth knowing

  • Over REST, a request counts as logged in only with the REST nonce, as REST lists already did. The site's own scripts send it.
  • Members and editors now get all the posts they may see in /wp/v2/search; the removed post-filter dropped some.
  • On sites whose default isn't public, embedded featured images without their own visibility come back as errors for anonymous visitors, as in the media list.

Testing

On local copies of two sites, comparing the released plugin with this branch. One is public by default with site groups (WPML, SearchWP, Yoast); the other is logged-in by default (WPML, Flatsome, Contextual Related Posts, Yoast).

  • Anonymous HTTP requests for pages, archives, feeds, endpoints, search and REST routes.
  • REST as admin, editor, subscriber, and members with and without matching groups.
  • SearchWP surfaces, oEmbed (REST and in-content embeds) and Yoast heads.

Not covered

  • Comments over REST on sites without Disable Comments.
  • Direct wp_oembed_get() callers, such as ACF oEmbed fields.
  • Yoast's XML sitemaps list restricted URLs (no content).

🤖 Generated with Claude Code

Phrase stopped getting restricted posts in 1.2.8: the new the_posts
backstop re-checked queries the visibility filter exempts. It now only
re-checks queries the filter set up, and bcc-login's internal query
vars are ignored when they come in with a query's own arguments.

Restricted posts could also be read without logging in:
- REST API: single posts from the posts controllers, oEmbed data and
  Yoast SEO's get_head route now follow the rules of the post's page.
  On sites whose default visibility isn't public, REST lists and
  search leave out post types without visibility support for visitors
  below the default. The old REST response check, which never matched
  these routes, is removed.
- Embeds of the site's own posts in content are decided per visitor
  when output, as the cached embed is shared by everyone.
- Feeds: ?post_types= is sanitised and limited to viewable post types,
  and translatedFrom only points to a published original with the
  same audience.
- bcc-login's endpoints are served at template_redirect, so they can't
  be combined with a feed; only the known endpoints skip the login
  check, and HEAD requests don't run them.
- SearchWP gets the rules as a plain post ID condition rather than a
  meta query, which skewed its ranking and slowed searches down; this
  covers its native search (also on CPT archives), live search, REST
  API and shortcodes.

On sites whose default visibility isn't public, archives, search and
core feeds are served when they were filtered for the visitor; single
views and anything unfiltered fall back to the site default as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@cs1m0n
cs1m0n requested a balanced review from Copilot October 13, 2026 09:01
@cs1m0n cs1m0n self-assigned this Oct 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The feed key still bypasses visibility checks on non-feed restricted URLs.

1 open finding
What changed in this PR

Hardens BCC Login content visibility across REST, feeds, endpoints, oEmbed, Yoast, and SearchWP while restoring Phrase access.

Changes:

  • Adds visibility enforcement across REST and search integrations.
  • Secures feed post types, translations, and endpoint dispatch.
  • Refines query filtering and exemptions.
File Description
class-bcc-login-visibility.php Expands visibility enforcement and query handling.
class-bcc-login-feed.php Restricts feed types and translation metadata.
class-bcc-login-endpoints.php Serves allowlisted endpoints before content rendering.

🧠 Review effort: Balanced


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

Comment thread plugins/bcc-login/includes/class-bcc-login-visibility.php
cs1m0n and others added 2 commits October 13, 2026 12:20
The key in ?id= skipped the login check on any URL, so it also opened
restricted pages. has_feed_key() now requires a feed request as well,
which covers the page check and the feed, embed and SearchWP exemptions
alike.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The visibility filters use SearchWP's index and source separator when
SearchWP is active. PHPStan doesn't load SearchWP, so it reported them
as unknown; they're declared in dependencies.stub like other external
symbols.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@cs1m0n
cs1m0n merged commit 6f97eb8 into master Oct 13, 2026
7 checks passed
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.

2 participants