DEV Community

Usama Ejaz
Usama Ejaz

Posted on Originally published at justblogged.com

What a Password-Protected Blog Post Still Leaks: WordPress 7.1.3 and Hugo Tested

A password on a WordPress post hides the body and the excerpt. That's it.

The title, the URL, the date and the featured image are still public, and WordPress keeps listing the post in its sitemap, its RSS feed and its REST API. On a static site it's looser... the host's password covers the page, but the generator already copied the post's text into your feed.

JustBlogged, the blog platform I run, doesn't have per-post passwords, so I wanted to see what the platforms that do actually hide. On October 21, 2026 I installed WordPress 7.1.3 locally with the default Twenty Twenty-Five theme, published one protected post with a featured image, and requested every public address that could mention it from a logged-out terminal. Then I did the same with a Hugo 0.168.0 build.

What a logged-out visitor got from WordPress

The post was titled "Client notes Q3". Its body and its hand-written excerpt each had a marker word in them, and it had an uploaded featured image. Neither marker word showed up anywhere. Everything else did:

Address What came back
The post URL 200, the heading "Protected: Client notes Q3", the password form, and the featured image
Home page The post in the latest-posts list, with title and featured image
/feed/ An item titled "Protected: Client notes Q3" with link and date, the description "There is no excerpt because this is a protected post.", and the password form as content
/wp-sitemap-posts-post-1.xml The post's URL, next to the public posts
/wp-json/wp/v2/posts/5 Title, link, date and featured_media ID, with content and excerpt empty and "protected": true
/wp-json/wp/v2/media?parent=5 The featured image's record and its direct file URL
The image file in /wp-content/uploads/ 200, no password asked
/client-notes/embed/ The prefixed title and the protected-excerpt sentence
Site search for a marker word No results

This matches WordPress's own password protection docs: the password replaces the content and excerpt and adds "Protected: " to the title. The same page warns that custom fields aren't covered, so a theme or plugin that prints post meta will print it on a protected post unless it checks post_password_required().

Search coming back empty is on purpose. For logged-out visitors WP_Query adds a post_password = '' condition to every search.

Two details for readers who do have the password:

  • The protected post was sent with Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private. A public post got no cache header at all. So a page cache that respects headers won't serve one reader's unlocked copy to the next person.
  • The password lives in a wp-postpass_ cookie that lasts 10 days by default, changeable with the post_password_expires filter. WordPress only remembers one password at a time, so opening two posts with different passwords means typing the first one again.

Getting the post out of the feed, sitemap and listings

The WordPress docs include a snippet that removes protected posts from every query except their own page. I saved it as a must-use plugin at wp-content/mu-plugins/hide-protected.php:

<?php
function exclude_protected( $where ) {
    global $wpdb;
    return $where .= " AND {$wpdb->posts}.post_password = '' ";
}

function exclude_protected_action( $query ) {
    if ( ! is_single() && ! is_page() && ! is_admin() ) {
        add_filter( 'posts_where', 'exclude_protected' );
    }
}
add_action( 'pre_get_posts', 'exclude_protected_action' );
Enter fullscreen mode Exit fullscreen mode

With it on, the post was gone from the home page, the feed, the sitemap and the REST API's post list. Its own URL still showed the password form.

Two things didn't change. /wp-json/wp/v2/posts/5 still returned the title to anyone asking for that ID, and the media endpoint still listed the image with its file URL.

The image is the part no query filter reaches. Files in wp-content/uploads are normally served straight by the web server without WordPress running, so there's no password check, and the default theme draws the featured image on the password page itself. If a picture has to stay private I wouldn't make it the featured image or upload it to a public site's media library at all. Same with the title and slug... if the subject alone gives it away, the title has to be neutral before you publish.

Static sites: the page is protected, the feed isn't

A static site has no server code to check a password, so protection comes from the host. Netlify has a shared-password visibility setting on Pro and Enterprise according to its knowledge base (as of October 21, 2026). Cloudflare Access can put a login in front of an app on a domain in your Cloudflare account. On your own server, basic auth does the same.

All of these protect the paths you give them. The generator already wrote the post into files at other paths.

In my Hugo build, with minimal templates and no theme, the default output put the post's text in both /index.xml and /posts/index.xml, and its URL in /sitemap.xml. A password rule on /posts/client-notes/ covers none of those.

One build option in front matter took it out of both feeds, the sitemap and the section listing, while the page still rendered:

---
title: Client notes Q3
build:
  list: never
---
Enter fullscreen mode Exit fullscreen mode

Hugo's build options docs say list: never keeps the page out of every page collection, and the built-in feed and sitemap templates read from those collections. A theme can build its feed, related posts or search index its own way though, so I'd rebuild and grep the output folder for a phrase from the post before trusting it.

Pagefind is the one that catches people. It reads the built HTML, not Hugo's collections, so an unlisted page that still renders still gets indexed. Its indexing docs recommend marking the pages you want indexed with data-pagefind-body, which drops every page without it. Other generators have the same problem in whichever plugin writes the feed, sitemap and search index.

Ghost doesn't do per-post passwords

Ghost restricts posts by membership instead: public, members only, paid members only, or specific tiers, per its protected content guide. Readers get in by signing in. The only shared password is Private Site Mode, which covers the whole site, and Ghost's RSS help page says the public feed is off while that's on, with a private feed URL under Settings, General, "Make this site private".

Which one fits

A WordPress post password is fine when the title and the fact the post exists can be public and only the body matters, like a client update or a family post. With the snippet above it stays out of the feed and sitemap. The featured image still needs to be neutral or gone.

A shared password also gets forwarded along with the link. It keeps search engines and casual visitors out, and tells you nothing about who actually read it.

When the post's existence has to stay hidden too, WordPress's Private status went further in my test: 404 for logged-out visitors, not in the sitemap, 401 from the REST API. But the docs limit private posts to Administrators and Editors, so it's for notes to yourself or a co-editor, not readers. For a group of readers you need a real login (Ghost members, or a host-level rule on a separate path) plus the feed, sitemap and search exclusions above.

Anything that would do real damage if the password leaked I wouldn't put on a public blog's server at all

Top comments (0)