[wp-trac] [WordPress Trac] #64560: Speculative Loading API can expose server path when using a theme directory outside the document root

WordPress Trac noreply at wordpress.org
Tue Jan 27 22:00:52 UTC 2026


#64560: Speculative Loading API can expose server path when using a theme directory
outside the document root
--------------------------+-----------------------------
 Reporter:  emrl          |      Owner:  (none)
     Type:  defect (bug)  |     Status:  new
 Priority:  normal        |  Milestone:  Awaiting Review
Component:  General       |    Version:
 Severity:  normal        |   Keywords:
  Focuses:                |
--------------------------+-----------------------------
 For several years we have been using `register_theme_directory()` to
 register a theme directory outside of the document root. I understand that
 it is unconventional, but it has worked perfectly with no issues until
 now. It was done to follow best practices to keep PHP code (functions.php,
 classes, templates) above any public directories.

 The Speculative Loading feature now excludes some base/default
 directories, and does not allow a real way to modify them via
 filter/action.

 `WP_URL_Pattern_Prefixer::get_default_contexts()` has no filter.

 `wp_speculation_rules_href_exclude_paths` filter specifically runs
 ''before'' those default contexts are added, so they cannot be removed.

 `wp_load_speculation_rules` action does not allow modification of any
 rules as `WP_Speculation_Rules` has no method for removing/modifying
 rules, it can only fully replace them. So to remove the `template` or
 `stylesheet` context, one would need to duplicate the entire main rule
 conditions from `wp_get_speculation_rules()`, which is fragile at best.

 Themes outside the document root is rare, but there doesn't seem to be
 anything preventing that, and everything else in WordPress seems to run
 just fine doing so.

 A quick fix has been for us to use the filters `stylesheet_directory_uri`
 and `template_directory_uri` to remove the absolute server paths, but this
 still leaves a potentially valid and real WordPress page URL on the
 excluded list.

 Example of output below, the server path is exposed in the HTML:
 `/home/USER/apps/APP_NAME/releases/485/theme-name/*`

 {{{
 <script type="speculationrules">
 {
   "prefetch": [
     {
       "source": "document",
       "where": {
         "and": [
           {
             "href_matches": "/*"
           },
           {
             "not": {
               "href_matches": [
                 "/wp-*.php",
                 "/wp-admin/*",
                 "/wp-content/uploads/*",
                 "/wp-content/*",
                 "/wp-content/plugins/*",
                 "/home/USER/apps/APP_NAME/releases/485/theme-name/*",
                 "/*\\?(.+)"
               ]
             }
           },
           {
             "not": {
               "selector_matches": "a[rel~=\"nofollow\"]"
             }
           },
           {
             "not": {
               "selector_matches": ".no-prefetch, .no-prefetch a"
             }
           }
         ]
       },
       "eagerness": "conservative"
     }
   ]
 }
 </script>
 }}}

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/64560>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list