Skip to content

Add optional asset proxy mode for servers with incorrect MIME types #53

@erseco

Description

@erseco

Issue: Optional asset proxy mode for servers with incorrect MIME configuration

Summary

Some WordPress installations serve extracted package assets directly from the uploads directory. This works well when the web server has a correct MIME configuration, but it can break packages when JavaScript files are returned as text/plain while X-Content-Type-Options: nosniff is enabled.

In that case browsers refuse to execute the scripts, even though the files exist and the HTTP status is 200.

Observed behavior

On an affected site, package HTML loads correctly through the REST content endpoint, but JavaScript assets loaded directly from the uploads directory are rejected by the browser.

Example response:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
X-Content-Type-Options: nosniff

The file body is valid JavaScript, but Chromium/Firefox reject it because strict MIME checking is enabled by nosniff.

Typical browser error:

Refused to execute script because its MIME type ('text/plain') is not executable, and strict MIME type checking is enabled.

Root cause

The plugin currently relies on the web server to serve static assets from the extracted package directory with the correct MIME type.

That is the fastest path and should remain the default. However, on servers where the uploads/files location is configured with an incomplete or incorrect MIME map, JavaScript files may be returned as text/plain.

Proposed solution

Add an optional setting to serve package assets through the plugin content proxy instead of linking them directly from uploads.

Suggested setting:

[ ] Serve package assets through the WordPress proxy

Suggested help text:

Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server.

This text should be translatable.

Implementation notes

  • Keep direct uploads URLs as the default for performance.
  • Add a settings checkbox, for example proxy_assets or similar.
  • When enabled, rewrite package asset URLs to the existing content proxy endpoint, not only HTML files.
  • The proxy should keep strict path validation:
    • validate package hash;
    • reject path traversal;
    • resolve paths with realpath();
    • ensure the resolved path stays inside the extracted package directory;
    • allow only known package asset extensions.
  • The proxy should emit explicit MIME types for at least:
    • .js, .mjs
    • .css
    • .json
    • .svg
    • .png, .jpg, .jpeg, .gif, .webp, .ico
    • .woff, .woff2, .ttf, .otf, .eot
    • .mp3, .mp4, .webm, .ogg, .ogv, .wav
    • .pdf
  • CSS url(...) references also need rewriting when CSS is served through the proxy, otherwise relative paths such as url(img/foo.svg) may resolve relative to the proxy endpoint path instead of the package CSS file.
  • External URLs such as YouTube embeds, https://, data:, blob:, protocol-relative URLs and anchors should not be proxied.

Acceptance criteria

  • A new translatable setting is available in the plugin settings to enable proxying package assets.
  • The setting is disabled by default.
  • When disabled, current direct asset behavior is preserved.
  • When enabled, package JavaScript files are served with application/javascript or another valid JavaScript MIME type.
  • CSS files are served with text/css.
  • Fonts and images are served with valid MIME types.
  • CSS url(...) references are correctly resolved relative to the CSS file location.
  • Path traversal and access outside the extracted package directory remain blocked.
  • Help text warns that this mode may be slower because assets go through PHP/WordPress instead of the web server.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions