Skip to content

Commit 7fd425c

Browse files
committed
Withhold password-protected article content from the REST API
The /wzkb/v1/knowledgebase, /wzkb/v1/knowledgebase/{id} and /wzkb/v1/search endpoints returned the content and excerpt of password-protected articles without the password. Return an empty excerpt and content when post_password_required() is true, add a 'protected' flag like core, and skip caching responses that include password-protected posts so an unlocked response is never served to another requester.
1 parent a54a091 commit 7fd425c

2 files changed

Lines changed: 59 additions & 12 deletions

File tree

‎includes/rest/class-rest-controller.php‎

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -520,14 +520,16 @@ public function get_knowledgebase_posts( WP_REST_Request $request ) {
520520
$response->header( 'X-WP-Total', (string) (int) $query->found_posts );
521521
$response->header( 'X-WP-TotalPages', (string) (int) $query->max_num_pages );
522522

523-
$this->cache_set(
524-
$cache_key,
525-
array(
526-
'body' => $posts,
527-
'total' => (int) $query->found_posts,
528-
'pages' => (int) $query->max_num_pages,
529-
)
530-
);
523+
if ( ! $this->has_password_protected_post( $query->posts ) ) {
524+
$this->cache_set(
525+
$cache_key,
526+
array(
527+
'body' => $posts,
528+
'total' => (int) $query->found_posts,
529+
'pages' => (int) $query->max_num_pages,
530+
)
531+
);
532+
}
531533

532534
return $response;
533535
}
@@ -558,6 +560,11 @@ public function get_single_knowledgebase_post( WP_REST_Request $request ) {
558560
);
559561
}
560562

563+
// Password-protected responses depend on the requester's password cookie, so never cache them.
564+
if ( '' !== $post->post_password ) {
565+
return rest_ensure_response( $this->prepare_post_for_response( $post, true ) );
566+
}
567+
561568
$cache_key = $this->get_cache_key(
562569
'kb_single',
563570
array(
@@ -742,7 +749,9 @@ public function get_search_results( WP_REST_Request $request ) {
742749
$results[] = $this->prepare_post_for_response( $post );
743750
}
744751

745-
$this->cache_set( $cache_key, $results );
752+
if ( ! $this->has_password_protected_post( $wp_query->posts ) ) {
753+
$this->cache_set( $cache_key, $results );
754+
}
746755

747756
return rest_ensure_response( $results );
748757
}
@@ -1013,6 +1022,9 @@ private function get_post_list_args() {
10131022
/**
10141023
* Prepare KB post data.
10151024
*
1025+
* Password-protected posts return an empty excerpt and content unless the
1026+
* requester has entered the post password, matching core's REST controller.
1027+
*
10161028
* @param \WP_Post $post Post object.
10171029
* @param bool $include_content Include full content flag.
10181030
* @return array
@@ -1023,25 +1035,50 @@ private function prepare_post_for_response( $post, $include_content = false ) {
10231035
return array();
10241036
}
10251037

1038+
$is_protected = '' !== $post->post_password;
1039+
$is_locked = $is_protected && post_password_required( $post );
1040+
10261041
$data = array(
10271042
'id' => (int) $post->ID,
10281043
'title' => get_the_title( $post ),
10291044
'slug' => $post->post_name,
1030-
'excerpt' => wp_trim_words( wp_strip_all_tags( $post->post_content ), 55 ),
1045+
'excerpt' => $is_locked ? '' : wp_trim_words( wp_strip_all_tags( $post->post_content ), 55 ),
10311046
'permalink' => get_permalink( $post ),
10321047
'products' => $this->format_terms( get_the_terms( $post, 'wzkb_product' ) ),
10331048
'sections' => $this->format_terms( get_the_terms( $post, 'wzkb_category' ) ),
10341049
'date' => mysql2date( DATE_RFC3339, $post->post_date_gmt, false ),
10351050
'modified' => mysql2date( DATE_RFC3339, $post->post_modified_gmt, false ),
1051+
'protected' => $is_protected,
10361052
);
10371053

10381054
if ( $include_content ) {
1039-
$data['content'] = apply_filters( 'the_content', $post->post_content );
1055+
$data['content'] = $is_locked ? '' : apply_filters( 'the_content', $post->post_content );
10401056
}
10411057

10421058
return $data;
10431059
}
10441060

1061+
/**
1062+
* Check whether any post in a list is password protected.
1063+
*
1064+
* Responses containing such posts vary with the requester's password cookie
1065+
* and must not be cached.
1066+
*
1067+
* @since 3.2.1
1068+
*
1069+
* @param array $posts Post objects.
1070+
* @return bool
1071+
*/
1072+
private function has_password_protected_post( array $posts ): bool {
1073+
foreach ( $posts as $post ) {
1074+
if ( $post instanceof \WP_Post && '' !== $post->post_password ) {
1075+
return true;
1076+
}
1077+
}
1078+
1079+
return false;
1080+
}
1081+
10451082
/**
10461083
* Format terms list.
10471084
*

‎readme.txt‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,15 @@ Release date: TBD
195195

196196
* [Pro] Reused cached Ask the Docs answers for reworded questions until their source articles change.
197197

198+
**Security**
199+
200+
* The `/wzkb/v1/knowledgebase` and `/wzkb/v1/search` REST endpoints returned the content and excerpt of password-protected articles without the password.
201+
202+
**Fixed**
203+
204+
* [Pro] GitHub imports and reimports stripped image captions from articles.
205+
* [Pro] Ask the Docs could cite another company's article.
206+
198207
= 3.2.0 =
199208

200209
Release date: 7 October 2026
@@ -219,6 +228,7 @@ Release post: https://webberzone.com/announcements/knowledge-base-v3-2/
219228

220229
* The Knowledge Base search results page size overrode the REST search endpoint's `limit` parameter.
221230
* Knowledge Base search forms showed duplicate suggestions when Better Search live search was also active.
231+
* [Pro] Help widget searches returned no articles with Better Search active, and the search box lost focus while typing.
222232
* Text in the Knowledge Base Alerts block could not be selected reliably in the editor.
223233
* Knowledge Base Alerts blocks without an alignment saved an invalid `text-align: none` inline style.
224234
* Settings repeater button labels used the wrong translation domain.
@@ -230,4 +240,4 @@ For the changelog of earlier versions, please refer to the [releases page on Git
230240
== Upgrade Notice ==
231241

232242
= 3.2.1 =
233-
Faster Pro Ask the Docs answers with a choice of model and a fallback provider, and better answer caching. Pro GitHub imports preserve image captions.
243+
Security release. Stops the REST API exposing password-protected articles. Also adds faster Pro Ask the Docs answers with a choice of model and a fallback provider, and better answer caching. Pro GitHub imports preserve image captions.

0 commit comments

Comments
 (0)