features-privacy.php
Privacy features
Author enumeration, REST users, oEmbed author, users sitemap, Browse Happy, dashboard events, local avatars, commenter IPs, embed privacy, remote editor content and the core AI client. All of them are global: they protect anonymous visitors or act on data and requests where no user context exists, so the exempt roles do not apply. Every hook name and priority below was checked against the WordPress 7.1.3 source.
Tags
Table of Contents
Functions
- jpkcom_harden_trim_register_privacy_features() : void
- Register the hooks of the active privacy features.
- jpkcom_harden_trim_is_author_enum_request() : bool
- Whether a request is an author enumeration probe.
- jpkcom_harden_trim_requested_url() : string
- The requested URL exactly as core's redirect_canonical() builds it.
- jpkcom_harden_trim_author_is_only_query_arg() : bool
- Whether `author` is the only argument of the requested URL's query.
- jpkcom_harden_trim_activitypub_answers_author() : bool
- Whether the ActivityPub plugin itself answers this `?author=N` request.
- jpkcom_harden_trim_author_enum_exempt() : bool
- Whether an author enumeration probe is let through.
- jpkcom_harden_trim_author_enum_guard_template() : mixed
- `template_include` callback (PHP_INT_MAX): the exempted probe must get ActivityPub's own document.
- jpkcom_harden_trim_block_author_enum() : void
- `template_redirect` callback: answer author enumeration probes with a 404.
- jpkcom_harden_trim_is_users_route() : bool
- Whether a REST route is the users collection or anything below it.
- jpkcom_harden_trim_filter_rest_users() : mixed
- `rest_pre_dispatch` callback: 401 for unauthenticated users requests.
- jpkcom_harden_trim_anonymize_oembed() : array<string, mixed>
- Replace the author in oEmbed data by the site.
- jpkcom_harden_trim_filter_oembed_response_data() : mixed
- `oembed_response_data` callback.
- jpkcom_harden_trim_filter_sitemap_provider() : mixed
- `wp_sitemaps_add_provider` callback: drop the users provider.
- jpkcom_harden_trim_is_browse_happy_url() : bool
- Whether a URL is the Browse Happy API (http or https).
- jpkcom_harden_trim_filter_browse_happy() : mixed
- `pre_http_request` callback: short-circuit the Browse Happy request.
- jpkcom_harden_trim_remove_events_widget() : void
- `wp_dashboard_setup` callback: remove "WordPress Events and News".
- jpkcom_harden_trim_local_avatar_data() : array<string, mixed>
- Point avatar data at the plugin's local SVG.
- jpkcom_harden_trim_filter_avatar_data() : mixed
- `pre_get_avatar_data` callback: local avatar instead of Gravatar.
- jpkcom_harden_trim_anonymize_ip() : string
- Anonymise an IP address: IPv4 to /24, IPv6 to /48.
- jpkcom_harden_trim_filter_comment_ip() : mixed
- `pre_comment_user_ip` callback.
- jpkcom_harden_trim_filter_comment_flood() : mixed
- `wp_is_comment_flood` callback: flood check against the anonymised IP.
- jpkcom_harden_trim_filter_comment_agent() : mixed
- `pre_comment_user_agent` callback: store no user agent.
- jpkcom_harden_trim_privacy_embed_src() : string
- Rewrite one iframe src for privacy.
- jpkcom_harden_trim_privacy_embed_html() : string
- Rewrite the src of every YouTube and Vimeo iframe in a piece of HTML.
- jpkcom_harden_trim_filter_embed_privacy() : mixed
- `embed_oembed_html` callback.
- jpkcom_harden_trim_filter_cover_embed() : mixed
- `render_block_core/cover` callback for embedded background videos.
- jpkcom_harden_trim_filter_oembed_proxy_response() : mixed
- `rest_post_dispatch` callback: rewrite the oEmbed proxy's preview HTML.
- jpkcom_harden_trim_filter_editor_settings() : mixed
- `block_editor_settings_all` callback: hide the Openverse media category.
- jpkcom_harden_trim_unregister_google_fonts() : void
- `init` callback: unregister core's Google Fonts collection.
Functions
jpkcom_harden_trim_register_privacy_features()
Register the hooks of the active privacy features.
jpkcom_harden_trim_register_privacy_features(array<string, mixed> $settings) : void
Parameters
- $settings : array<string, mixed>
-
Effective settings.
Tags
jpkcom_harden_trim_is_author_enum_request()
Whether a request is an author enumeration probe.
jpkcom_harden_trim_is_author_enum_request(array<string, mixed> $query, bool $logged_in, string $permalink_structure, bool $is_admin) : bool
Plain permalinks are left alone (there ?author=N is the real author
link), as are logged-in users and the admin. Whether a probe is exempt
(ActivityPub) is decided separately, see
jpkcom_harden_trim_author_enum_exempt().
Parameters
- $query : array<string, mixed>
-
Parsed request query vars.
- $logged_in : bool
-
Whether a user is logged in.
- $permalink_structure : string
-
The permalink structure option.
- $is_admin : bool
-
Whether this is an admin request.
Tags
Return values
bool —True for an anonymous ?author=N request with pretty permalinks.
jpkcom_harden_trim_requested_url()
The requested URL exactly as core's redirect_canonical() builds it.
jpkcom_harden_trim_requested_url() : string
7.1.3 canonical.php:69-73: scheme from is_ssl(), then the raw Host
header and the raw REQUEST_URI, concatenated unchecked; no Host header,
no URL. This is the $requested_url ActivityPub's
Router::redirect_canonical() parses, so a Host header such as
example.com# moves the whole REQUEST_URI into the fragment there.
A non-string server value (no web SAPI sends one) gives '' as well.
Tags
Return values
string —The URL, or '' when core builds none.
jpkcom_harden_trim_author_is_only_query_arg()
Whether `author` is the only argument of the requested URL's query.
jpkcom_harden_trim_author_is_only_query_arg(string $requested_url) : bool
Mirrors ActivityPub's own check in Router::redirect_canonical()
(ActivityPub 9.3.1, class-router.php:300-330), input included: it takes
the query of core's $requested_url (see
jpkcom_harden_trim_requested_url(), not REQUEST_URI alone) with
wp_parse_url(), parses it with wp_parse_args(), ignores activitypub
and stamp, and suppresses core's redirect to /author/author, remains. No query (also one
hidden in a fragment by the Host header) or any further argument (also
one WordPress ignores) lets that redirect through.
Parameters
- $requested_url : string
-
The requested URL as core builds it.
Tags
Return values
bool —True for ?author=N, optionally with activitypub/stamp.
jpkcom_harden_trim_activitypub_answers_author()
Whether the ActivityPub plugin itself answers this `?author=N` request.
jpkcom_harden_trim_activitypub_answers_author(array<string, mixed> $query, string $requested_url) : bool
ActivityPub (wordpress.org slug activitypub, read against 9.3.1)
answers ?author=N with the actor document only when all of these hold,
each decided by its own public API, never by a header test of ours:
\Activitypub\is_activitypub_request()(functions-request.php:70):?activitypub, or an Accept header whose highest-quality type is activity+json or ld+json with the ActivityStreams profile (q-aware,q=0refuses), plus its filter. Otherwise it does not suppress core's canonical redirect (class-router.php:301).\Activitypub\should_negotiate_content()(functions-request.php:79): otherwise it renders the theme template (class-router.php:92).authoris the only argument in the query of the requested URL as core builds it from the Host header and REQUEST_URI (see jpkcom_harden_trim_author_is_only_query_arg()); otherwise core's redirect goes through.\Activitypub\user_can_activitypub( N )(functions-user.php:90): a user who is no actor gets no transformer (transformer/class-factory.php:109) and the theme renders the author archive.- The request reaches
template_include, where ActivityPub renders its document (class-router.php:24). Core's template loader answers robots, favicon, feeds and trackbacks before that filter (template-loader.php:40-62), and sitemaps exit ontemplate_redirect; a feed path such as/feed/?author=Nkeeps the raw query atauthoralone, yet core serves the author's feed (display name, by default the login). Any of their query vars (feed,tb,robots,favicon,sitemap,sitemap-stylesheet), from path, query or POST body, refuses.
The functions are looked up at call time. If any of them is missing (ActivityPub not loaded, or a version with a different API), the answer is false: an unknown ActivityPub is not trusted to answer.
Parameters
- $query : array<string, mixed>
-
Parsed request query vars.
- $requested_url : string
-
The requested URL as core builds it (jpkcom_harden_trim_requested_url()).
Tags
Return values
bool —True when ActivityPub answers the request with its own document.
jpkcom_harden_trim_author_enum_exempt()
Whether an author enumeration probe is let through.
jpkcom_harden_trim_author_enum_exempt(array<string, mixed> $query, string $accept[, string $request_uri = '' ][, string $requested_url = '' ]) : bool
By default only when the ActivityPub plugin itself answers this very
request with the actor document (see
jpkcom_harden_trim_activitypub_answers_author()): then ?author=N is
the actor ID other servers resolve, and ActivityPub publishes the
actor's name by design. Every other probe, whatever its Accept header,
gets the 404, because core would redirect it to /author/
Parameters
- $query : array<string, mixed>
-
Parsed request query vars.
- $accept : string
-
The Accept request header (passed to the filter).
- $request_uri : string = ''
-
The raw REQUEST_URI (passed to the filter).
- $requested_url : string = ''
-
The requested URL as core builds it (jpkcom_harden_trim_requested_url()); '' never exempts.
Tags
Return values
bool —True when the probe must not get the 404.
jpkcom_harden_trim_author_enum_guard_template()
`template_include` callback (PHP_INT_MAX): the exempted probe must get ActivityPub's own document.
jpkcom_harden_trim_author_enum_guard_template(mixed $template) : mixed
Added only for a request that was exempted because ActivityPub answers
it. ActivityPub still falls back to the theme template on some paths
it decides only while rendering: under Authorized Fetch an unsigned
request gets status 401 and the theme template (9.3.1
class-router.php:211-215), which for ?author=N is the author archive
with the user's name and nicename. ActivityPub's own templates live in
its plugin directory (ACTIVITYPUB_PLUGIN_DIR); any other template turns
the response into the 404 the probe would have got.
Parameters
- $template : mixed
-
Template path chosen so far.
Tags
Return values
mixed —The template, or the theme's 404 template.
jpkcom_harden_trim_block_author_enum()
`template_redirect` callback: answer author enumeration probes with a 404.
jpkcom_harden_trim_block_author_enum() : void
Reads the parsed request ($wp->query_vars), not $wp_query: WP_Query
fills its own author from author_name on pretty author archives,
which must keep working. The query is emptied (see
jpkcom_harden_trim_force_404()), so the theme's 404 template renders
and neither a feed nor a fallback index lists the author's posts;
redirect_canonical() sees is_404() and has nothing to guess from.
Tags
jpkcom_harden_trim_is_users_route()
Whether a REST route is the users collection or anything below it.
jpkcom_harden_trim_is_users_route(string $route) : bool
Case-insensitive like core's route matching; a trailing slash is tolerated.
The anchor must keep core's semantics: WP_REST_Server::match_request_to_handler()
(7.1.3 class-wp-rest-server.php:1172) matches with '@^' . $route . '$@i',
no D modifier, so its $ also matches before a trailing "\n", and
rest_api_loaded() only strips slashes (untrailingslashit()). Core
therefore serves ?rest_route=/wp/v2/users%0A as the user list. Do
not "tighten" this to \z: that would let exactly that request through.
Parameters
- $route : string
-
REST route, e.g.
/wp/v2/users/1.
Tags
Return values
bool —True for /wp/v2/users and /wp/v2/users/….
jpkcom_harden_trim_filter_rest_users()
`rest_pre_dispatch` callback: 401 for unauthenticated users requests.
jpkcom_harden_trim_filter_rest_users(mixed $result[, mixed $server = null ][, mixed $request = null ]) : mixed
Decided at dispatch time, after cookie+nonce and application-password authentication. An error set earlier (rate limit, another plugin) is kept; any other short-circuit result is replaced.
Parameters
- $result : mixed
-
Short-circuit result so far (null to continue).
- $server : mixed = null
-
REST server (unused).
- $request : mixed = null
-
The request.
Tags
Return values
mixed —The result, or a WP_Error with status 401.
jpkcom_harden_trim_anonymize_oembed()
Replace the author in oEmbed data by the site.
jpkcom_harden_trim_anonymize_oembed(array<string, mixed> $data) : array<string, mixed>
Parameters
- $data : array<string, mixed>
-
oEmbed response data.
Tags
Return values
array<string, mixed> —The data; only existing author keys are changed.
jpkcom_harden_trim_filter_oembed_response_data()
`oembed_response_data` callback.
jpkcom_harden_trim_filter_oembed_response_data(mixed $data) : mixed
Parameters
- $data : mixed
-
oEmbed response data.
Tags
Return values
mixed —The anonymised data, or the input when it is no array.
jpkcom_harden_trim_filter_sitemap_provider()
`wp_sitemaps_add_provider` callback: drop the users provider.
jpkcom_harden_trim_filter_sitemap_provider(mixed $provider[, mixed $name = '' ]) : mixed
Parameters
- $provider : mixed
-
Sitemap provider.
- $name : mixed = ''
-
Provider name.
Tags
Return values
mixed —False for users (core then skips it), else the provider.
jpkcom_harden_trim_is_browse_happy_url()
Whether a URL is the Browse Happy API (http or https).
jpkcom_harden_trim_is_browse_happy_url(string $url) : bool
Parameters
- $url : string
-
Request URL.
Tags
Return values
bool —True for api.wordpress.org/core/browse-happy/….
jpkcom_harden_trim_filter_browse_happy()
`pre_http_request` callback: short-circuit the Browse Happy request.
jpkcom_harden_trim_filter_browse_happy(mixed $pre[, mixed $args = array() ][, mixed $url = '' ]) : mixed
The error message is never shown (core only checks is_wp_error()), so it is not translated; translating here could load the text domain early.
Parameters
- $pre : mixed
-
Short-circuit value so far (false to continue).
- $args : mixed = array()
-
Request arguments (unused).
- $url : mixed = ''
-
Request URL.
Tags
Return values
mixed —A WP_Error for the Browse Happy request, else $pre.
jpkcom_harden_trim_remove_events_widget()
`wp_dashboard_setup` callback: remove "WordPress Events and News".
jpkcom_harden_trim_remove_events_widget() : void
Without the widget its scripts never request events or news.
Tags
jpkcom_harden_trim_local_avatar_data()
Point avatar data at the plugin's local SVG.
jpkcom_harden_trim_local_avatar_data(array<string, mixed> $args) : array<string, mixed>
Parameters
- $args : array<string, mixed>
-
Avatar data arguments.
Tags
Return values
array<string, mixed> —The arguments with url and found_avatar set.
jpkcom_harden_trim_filter_avatar_data()
`pre_get_avatar_data` callback: local avatar instead of Gravatar.
jpkcom_harden_trim_filter_avatar_data(mixed $args) : mixed
Setting url makes get_avatar_data() return before it hashes the
e-mail address. A URL another plugin already set is kept.
Parameters
- $args : mixed
-
Avatar data arguments.
Tags
Return values
mixed —The arguments.
jpkcom_harden_trim_anonymize_ip()
Anonymise an IP address: IPv4 to /24, IPv6 to /48.
jpkcom_harden_trim_anonymize_ip(string $ip) : string
An IPv4-mapped IPv6 address (::ffff:a.b.c.d, as dual-stack listeners
report IPv4 clients) is anonymised as the IPv4 address it carries;
keeping its first 48 bits would store :: for every such client. Core's
sanitiser keeps commas and spaces (comment.php:2424), so a proxy list
anonymises its first valid address.
Parameters
- $ip : string
-
IP address, or a comma-separated list of them.
Tags
Return values
string —The anonymised address, or '' when no valid IP is found.
jpkcom_harden_trim_filter_comment_ip()
`pre_comment_user_ip` callback.
jpkcom_harden_trim_filter_comment_ip(mixed $ip) : mixed
Re-checks the setting of the current site: after switch_to_blog() a comment stored on another site of the network follows that site's choice, not the one the request booted with.
Parameters
- $ip : mixed
-
Commenter IP.
Tags
Return values
mixed —The anonymised IP ('' for anything that is no IP), or $ip
unchanged when the current site does not anonymise.
jpkcom_harden_trim_filter_comment_flood()
`wp_is_comment_flood` callback: flood check against the anonymised IP.
jpkcom_harden_trim_filter_comment_flood(mixed $is_flood[, mixed $ip = '' ][, mixed $email = '' ][, mixed $date = '' ][, mixed $avoid_die = false ]) : mixed
wp_check_comment_flood() (7.1.3 comment.php:909) looks for a recent
comment with comment_author_IP = <live IP> or the same e-mail address.
While comment_ip is on, stored rows hold the shortened address, so for
guests only the e-mail clause would still match and a guest changing
the e-mail address on every comment was never throttled. This runs the
same core check once more with the shortened form, so the IP clause
works again, per /24 (IPv4) or /48 (IPv6) network.
Skipped when core already found a flood, for logged-in users (core checks the user ID), when the IP is already in its stored form, and when the site removed core's flood check.
Parameters
- $is_flood : mixed
-
Flood verdict so far.
- $ip : mixed = ''
-
Live commenter IP.
- $email : mixed = ''
-
Commenter e-mail address.
- $date : mixed = ''
-
GMT date of the new comment.
- $avoid_die : mixed = false
-
Whether to return instead of calling wp_die().
Tags
Return values
mixed —The verdict.
jpkcom_harden_trim_filter_comment_agent()
`pre_comment_user_agent` callback: store no user agent.
jpkcom_harden_trim_filter_comment_agent(mixed $agent) : mixed
Parameters
- $agent : mixed
-
Commenter user agent.
Tags
Return values
mixed —'' while the current site anonymises comments, else $agent.
jpkcom_harden_trim_privacy_embed_src()
Rewrite one iframe src for privacy.
jpkcom_harden_trim_privacy_embed_src(string $src) : string
YouTube (www. or bare host) moves to youtube-nocookie.com; a Vimeo
player URL gets dnt=1, and an existing dnt parameter with any other
value (dnt=0 keeps tracking on) is set to 1. The
separator follows the src's own escaping (&, & or &) and
the parameter goes before a fragment.
Parameters
- $src : string
-
Raw attribute value.
Tags
Return values
string —The rewritten value.
jpkcom_harden_trim_privacy_embed_html()
Rewrite the src of every YouTube and Vimeo iframe in a piece of HTML.
jpkcom_harden_trim_privacy_embed_html(string $html) : string
Only src attributes of <iframe> tags are touched; links and text
stay as they are. Idempotent.
Parameters
- $html : string
-
Embed or block HTML.
Tags
Return values
string —The rewritten HTML.
jpkcom_harden_trim_filter_embed_privacy()
`embed_oembed_html` callback.
jpkcom_harden_trim_filter_embed_privacy(mixed $html) : mixed
Parameters
- $html : mixed
-
Embed HTML.
Tags
Return values
mixed —The rewritten HTML, or the input when it is no string.
jpkcom_harden_trim_filter_cover_embed()
`render_block_core/cover` callback for embedded background videos.
jpkcom_harden_trim_filter_cover_embed(mixed $content[, mixed $block = array() ]) : mixed
Parameters
- $content : mixed
-
Rendered block HTML.
- $block : mixed = array()
-
Parsed block.
Tags
Return values
mixed —The rewritten HTML for embed-video covers, else the input.
jpkcom_harden_trim_filter_oembed_proxy_response()
`rest_post_dispatch` callback: rewrite the oEmbed proxy's preview HTML.
jpkcom_harden_trim_filter_oembed_proxy_response(mixed $result[, mixed $server = null ][, mixed $request = null ]) : mixed
Covers fresh and cached proxy responses; the cached copy itself is not changed.
Parameters
- $result : mixed
-
REST response.
- $server : mixed = null
-
REST server (unused).
- $request : mixed = null
-
The request.
Tags
Return values
mixed —The response.
jpkcom_harden_trim_filter_editor_settings()
`block_editor_settings_all` callback: hide the Openverse media category.
jpkcom_harden_trim_filter_editor_settings(mixed $settings) : mixed
Parameters
- $settings : mixed
-
Block editor settings.
Tags
Return values
mixed —The settings.
jpkcom_harden_trim_unregister_google_fonts()
`init` callback: unregister core's Google Fonts collection.
jpkcom_harden_trim_unregister_google_fonts() : void
Checked first, because unregistering a collection that is not registered (removed by another plugin) raises _doing_it_wrong. The check goes through the public get_font_collections(): WP_Font_Library::is_collection_registered() is private (7.1.3, class-wp-font-library.php:96) and calling it is a fatal Error.