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
200HTML document with noSet-Cookieheader - 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
DONOTCACHEPAGEis 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:
GETrequests- 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_optionscapability. - Saving (
admin_post_bb_cache_save) and every tool (admin_post_bb_cache_tool) checkmanage_optionsand 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
realpathand removes files only when the folder lies insidewp-content/cache/bb-cache/pages. - After a tool runs, the redirect target is limited by
wp_safe_redirectto 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.phpis written withvar_export, so a setting's value ends up as a PHP string literal and cannot become code..htaccesstoken filter (0.2.1). Onlyreject_cookiesentries 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.phpwas 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_dirsetting lets an administrator choose the folder critical CSS is read from. Onlybase.css,section-<layout>.css,masthead.css,post-<ID>.cssand<key>.cssare 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, afterrealpath, to a readable file insidewp-contentorwp-includes. - Stylesheets are processed only when the URL ends in
.css. Scripts are processed when the tag has asrcand a JavaScript type; the script path has no extension check in 0.3.0. </styleescape (0.2.1). When a stylesheet is inlined, any</styletext 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_ZONEandBB_CACHE_CF_TOKENconstants when defined, otherwise from the settings. A malformed value counts as missing. - Prefer the constant. A token defined in
wp-config.phpnever 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_settingsoption. 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
Bearerheader toapi.cloudflare.com. It is not written toconfig.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.