Skip to content

Security model#

This page states what BB Cache stores, serves, reads and calls, so the behaviour can be checked against the code. To report an issue, see SECURITY.md in the repository root.

What the page cache stores#

A response is written to disk only when all of these hold (BB_Cache_Page_Cache::is_storable()):

  • the request passed the drop-in's checks: GET, no session cookie, no unknown query string, the site's own host
  • the visitor is not logged in
  • the response is a 200 HTML document with no Set-Cookie header
  • the request is not a preview, search, 404, feed, trackback, robots.txt, admin or Customizer request
  • the post is not waiting for a password, and DONOTCACHEPAGE is not set
  • with WooCommerce active, the page is not cart, checkout or account

Private posts answer anonymous visitors with a 404, so they are never stored.

Cached pages are plain files under wp-content/cache/bb-cache/pages. Like any file under wp-content they can be requested directly by URL. They contain only what an anonymous visitor would have received.

What the drop-in serves#

inc/serve.php serves a stored file only for:

  • GET requests
  • without a cookie whose name starts with an entry of reject_cookies (WordPress login, post password, commenter and WooCommerce session cookies by default)
  • with no query string, or only marketing parameters and allow-listed parameters
  • for a host name on the allow-list

Host allow-list (0.2.1). The drop-in serves and stores only for the site's own host name, with and without www. (filter bb_cache_hosts). A request with any other Host header neither creates a cache folder nor reads from one; it is answered by WordPress with X-BB-Cache: MISS (host). The list is derived from the site address only in trusted contexts: a settings save, activation, the reinstall tool, WP-CLI, or other code that calls BB_Cache_Settings::save() or write_config() on an administrator's behalf, such as a deploy tool. It is never derived on an ordinary front-end request, because some stacks build the site address from the request's Host header. An empty list turns the check off. See Hosting notes.

Paths. A request path containing .. or a NUL byte is never cached. The cache folder is resolved from the drop-in's own location.

Protocol variant. The -https variant is chosen when HTTPS is on, the port is 443, or the request carries X-Forwarded-Proto: https. The header is taken as sent.

Query strings and load#

Any query string with an unknown parameter bypasses the cache; ?bb_nocache=1 and ?bb_raw=1 are no different from ?x=1 in that respect. This is by design: a visitor gains nothing from the debug flags that an arbitrary parameter would not give. It also means a client can always force an uncached render by adding a parameter. Rate limiting is left to the web server, CDN or firewall in front of the site.

?bb_raw=1 shows the page without optimisations. It exposes nothing that the server did not already send before the optimisers ran.

Admin actions#

  • The settings screen and the tools are registered for users with the manage_options capability.
  • Saving (admin_post_bb_cache_save) and every tool (admin_post_bb_cache_tool) check manage_options and a nonce. Neither action is registered for logged-out users.
  • The plugin registers no REST route and no AJAX action.
  • Purge path guard (0.2.1). Purging a URL resolves the target folder with realpath and removes files only when the folder lies inside wp-content/cache/bb-cache/pages.
  • After a tool runs, the redirect target is limited by wp_safe_redirect to a path on the site.

Settings and generated files#

  • Each setting is sanitised by type: integers are cast, lists are split into trimmed lines, choice fields are checked against their allowed values.
  • config.php is written with var_export, so a setting's value ends up as a PHP string literal and cannot become code.
  • .htaccess token filter (0.2.1). Only reject_cookies entries made of letters, digits, _ and - are written into the rewrite block, so a malformed entry cannot break the site's rewrite rules.
  • Config self-heal (0.2.1). When config.php was written by another plugin version, it is rewritten on the next request with the current keys. The existing host list is kept; a front-end request never derives one.
  • The critical_dir setting lets an administrator choose the folder critical CSS is read from. Only base.css, section-<layout>.css, masthead.css, post-<ID>.css and <key>.css are read from it, and their contents are inlined into pages.

Files the optimisers read#

  • Combining, minifying and inlining read a file only when its URL is on the site's own host (or the content URL's host; filter bb_cache_local_hosts) and the path resolves, after realpath, to a readable file inside wp-content or wp-includes.
  • Stylesheets are processed only when the URL ends in .css. Scripts are processed when the tag has a src and a JavaScript type; the script path has no extension check in 0.3.0.
  • </style escape (0.2.1). When a stylesheet is inlined, any </style text in it is escaped, so the sheet cannot end the <style> element and inject markup.
  • Critical CSS fragments are files deployed with the theme. The plugin never generates them on the server.
  • If an optimiser throws an error, it is logged and the unmodified HTML is sent.

Optimisers do not run for logged-in users unless optimize_logged_in is on.

Outbound requests#

Feature Destination Notes
Cache preload The site's own sitemap and page URLs The background queue keeps only URLs on the home host. wp bb-cache preload --sync fetches every URL the sitemap lists without that filter. TLS verification is off for the page fetches
Local Google Fonts fonts.googleapis.com, fonts.gstatic.com Only for font stylesheet URLs found in the page, and only fonts.gstatic.com files referenced by that CSS are downloaded
Varnish purge The configured varnish_host Non-blocking, TLS verification off
Cloudflare purge and verify api.cloudflare.com Only when credentials are configured
Cloudflare edge check The site's home URL, or the URL given to wp bb-cache cloudflare check Two GET requests

Nothing else leaves the server. There is no remote service and no telemetry.

Visitors' browsers make one kind of third-party request the plugin introduces: with lazyload_youtube on, the preview image of each YouTube embed is loaded from i.ytimg.com. With host_google_fonts on, the Google Fonts link remains in the page until the local copy has been fetched.

Cloudflare token handling#

  • The zone ID and token are read from the BB_CACHE_CF_ZONE and BB_CACHE_CF_TOKEN constants when defined, otherwise from the settings. A malformed value counts as missing.
  • Prefer the constant. A token defined in wp-config.php never sits in the database and does not travel with a database export or push.
  • A token saved in the settings is stored in plain text in the bb_cache_settings option. It is also printed into the settings form, as a password field on the Cloudflare tab and as a hidden field on the other tabs, so anyone who can open the settings screen (manage_options) can read it from the page source.
  • The token is sent only as a Bearer header to api.cloudflare.com. It is not written to config.php, to the purge log or to the PHP error log, and no command or screen prints it. The status line shows only where it comes from.
  • The purge log stores the time, the action, the count, the result, Cloudflare's error text and up to three purged URLs.
  • Give the token one permission, Zone > Cache Purge > Purge, limited to the one zone. With that scope a leaked token can purge the zone's cache and nothing else.

Edge caching#

With an edge TTL set, only responses the page cache serves or would store are marked public; every other front-end HTML response without an existing Cache-Control header is marked private. The Cache Rule the plugin prints for the zone excludes requests carrying the session cookies in reject_cookies. Without those conditions a logged-in visitor could be served the edge's anonymous copy. See Cloudflare.

Data the plugin keeps#

Where What
Option bb_cache_settings All settings, including the Cloudflare token if saved there
Option bb_cache_cf_log Last 20 Cloudflare purge results
Option bb_cache_preload_queue Pending preload URLs and progress
Option bb_cache_last_purge Time of the last full purge
Option bb_cache_img_dims Cached image dimensions (when image_dimensions is on)
Transients WebP sibling map, attachment ID map, font fetch markers, admin notices, the last edge check
wp-content/cache/bb-cache/ config.php, cached pages, minified files, local fonts

Uninstalling deletes the five options and the cache folder. Transients expire on their own.