Skip to content

/index.php serves homepage (not 404) on sites without resources/sites.yaml, poisoning the static cache #14644

Description

@dannyuk1982

Bug description

On Statamic 6 (confirmed on v6.16.0), a site that doesn't have a resources/sites.yaml file will treat a direct request to /index.php as a valid request for the homepage and respond with 200 instead of 404. All URLs generated on that response are prefixed with /index.php/.... When full static caching is enabled, that response is written to disk and served as 200 to every subsequent /index.php request indefinitely.

Impact: crawlers spider the /index.php/{slug} link graph, every cached page on those paths contains the same broken prefix, and the control panel becomes inaccessible via the affected route. Statamic 5 sites correctly return a styled 404 in this scenario.

Root cause appears to be:

  • Sites::getFallbackConfig() (src/Sites/Sites.php:130) defaults the site URL to '/' (relative) when resources/sites.yaml is absent.
  • Site::absoluteUrl() (src/Sites/Site.php:78) calls URL::makeAbsolute() on that relative URL.
  • URL::makeAbsolute() prepends the result of URL::getRequestRootUrl() (src/Facades/Endpoint/URL.php:468), which uses Laravel's url()->to('/'). On a direct /index.php request, Laravel/Symfony resolves the request base URL to /index.php, so the "absolute site URL" becomes https://host/index.php for that request.
  • Data::findByRequestUrl() (src/Data/DataRepository.php:44) then strips the inflated site URL from the request URL, leaving '', which URL::makeRelative() normalises to '/'. That matches the homepage entry, and Statamic serves it as 200.

Regression source
Introduced in PR #11840 ("[6.x] Normalize and configure trailing slashes in URLs"), commit 1fbc7f2, first shipped in v6.0.0-alpha.7. That PR refactored Site::absoluteUrl() from request()->getSchemeAndHttpHost() . $url to URL::makeAbsolute($url). The previous form pinned the site base to the request's scheme + host only; the new form delegates to URL::makeAbsolute(), which prepends url()->to('/') — and Laravel's request root includes the base URL (/index.php) on direct front-controller requests, so the site's "absolute URL" is no longer invariant across request entry points.

Workaround:

Create resources/sites.yaml with an explicit absolute URL:

default:
  name: "{{ config:app:name }}"
  url: "{{ config:app:url }}"
  locale: "{{ config:app:locale }}"

This makes Site::url() return an already-absolute URL, so URL::makeAbsolute() short-circuits and never calls getRequestRootUrl(). After applying the fix, the static cache must be cleared (php artisan statamic:static:clear plus rm -rf public/static/*) to evict the poisoned entries.

Suggested fix:
Either default the fallback url in Sites::getFallbackConfig() to config('app.url') instead of '/', or have URL::getRequestRootUrl() strip a trailing /index.php so the site base is invariant to whether the request hit the front controller directly.

How to reproduce

  1. Install a fresh Statamic 6 site (or use any existing site that doesn't have resources/sites.yaml).
  2. Ensure APP_URL is set to an absolute URL in .env.
  3. Confirm resources/sites.yaml does not exist: ls resources/sites.yaml → no such file.
  4. Start the app (php artisan serve or any webserver that doesn't itself block /index.php).
  5. Run:
    curl -I http://localhost:8000/index.php
  6. Expected: HTTP/1.1 404 Not Found
    Actual: HTTP/1.1 200 OK with the homepage body.
  7. Inspect the body — every generated link uses /index.php/... as its base.
  8. Optional: enable full static caching (STATAMIC_STATIC_CACHING_STRATEGY=full), hit /index.php once, and observe that public/static/index.php_.html is created. Every subsequent request to /index.php is now served from disk with the prefix baked in.

Logs

Environment

Environment
Laravel Version: 13.7.0
PHP Version: 8.4.20
Composer Version: 2.9.7
Environment: production
Debug Mode: OFF
Maintenance Mode: OFF
Timezone: UTC
Locale: en

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: redis
Database: sqlite
Logs: stack / single
Mail: mailgun
Queue: sync
Session: file

Storage
public/storage: NOT LINKED

Statamic
Addons: 0
License Key: Set
Sites: 1
Stache Watcher: Disabled (auto)
Static Caching: full
Version: 6.16.0 PRO

Installation

Fresh statamic/statamic site via CLI

Additional details

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions