WP Manifestindependent plugin directory
manifest / performance / oxpulse-imager

OXPulse Imager releases

Optional bring-your-own imgproxy image delivery for WordPress. Disabled by default. No SaaS, no telemetry, no FFI.

by Anatoly Koptev · github.com/anatolykoptev/oxpulse-imager · website

0stars
2release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/anatolykoptev/oxpulse-imager/releases/download/v0.1.10/oxpulse-imager.zip

Readme

OXPulse Imager

Optional bring-your-own imgproxy image delivery for WordPress.

Generates signed, deterministic imgproxy URLs for approved local origins while preserving the original URL whenever configuration, source policy, signing, or delivery cannot safely proceed. Disabled by default. No SaaS, no telemetry, no FFI.

How it works

The plugin rewrites image URLs on the frontend (never in admin) to signed imgproxy URLs. imgproxy fetches the original image, transforms it (resize, format conversion, quality), and serves the result. The original image is never modified.

Format negotiation (2026 industry standard)

The default output format is auto, which uses Accept header content negotiation — the same approach used by Cloudflare Images, Cloudinary, Imgix, and Vercel:

  1. Browser sends Accept: image/avif,image/webp,... on every image request
  2. imgproxy inspects the Accept header and serves the best format the browser supports:
    • image/avif → AVIF (~50% smaller than JPEG)
    • image/webp → WebP (~30% smaller)
    • neither → original format (JPEG fallback)
  3. Response includes Vary: Accept so caches store one variant per format

No <picture> / <source> tags needed. One URL serves the optimal format per browser.

Content-Disposition

The plugin adds a fn: (filename) option to every imgproxy URL so that "Save As" in browsers produces a meaningful filename. In auto mode, the original filename is preserved; in explicit format mode (avif/webp), the extension is replaced to match.

Requirements

  • PHP 8.3+
  • WordPress 6.2+
  • imgproxy v4+ endpoint (bring your own)

imgproxy server configuration

For auto format mode (Accept header negotiation), configure your imgproxy server with:

IMGPROXY_AUTO_AVIF=true
IMGPROXY_AUTO_WEBP=true

Without these, auto mode will serve the original format (no conversion). Use the "Test Connection" button in the plugin settings to verify that AVIF negotiation is working.

CDN / caching proxy configuration

Critical: When using auto format mode, your CDN or caching proxy MUST include the Accept header in the cache key. Otherwise, the CDN will serve an AVIF response to a browser that requested WebP (or vice versa).

Nginx cache key

proxy_cache_key "$scheme$proxy_host$request_uri$http_accept";

CDN settings

  • Include Accept header in cache key
  • Do not strip or normalize the Accept header
  • Honor the Vary: Accept response header from imgproxy

Security note

Headers cannot be signed. An attacker can bypass your CDN cache by varying the Accept header. This is a cache-poisoning vector, not a security vulnerability in the plugin — imgproxy will still only serve allowed formats. Configure your CDN to limit the number of distinct Accept header variants it will cache.

Local delivery (Phase 6 — standard/shared hosting)

When no imgproxy endpoint is configured, the plugin switches to LocalBackend mode: on-disk WebP delivery using PHP (Imagick→GD) with no daemon required. Cache files are served as static files; misses are handled by a self-contained oxpulse-img.php endpoint.

Apache (.htaccess)

The plugin generates .htaccess rules that rewrite missing cache files to the miss-endpoint. Requires mod_rewrite + AllowOverride. A capability-test auto-detects whether rewrite is available and falls back to the output-buffer mode if not.

nginx

nginx does not support .htaccess. Use the output-buffer fallback (automatic) or add this try_files snippet to your server block. Also add a deny-PHP location for the cache dir (belt-and-braces) BEFORE it:

# Belt-and-braces: never execute scripts inside the cache dir.
location ~* ^/wp-content/cache/oxpulse/.*\.(php|phtml)$ { deny all; }

# Serve existing cache files directly; on miss, route to the endpoint
# with the key extracted from the filename.
location ~* ^/wp-content/cache/oxpulse/([0-9a-f]+)/(.+)\.(webp|avif)$ {
    add_header Vary Accept;
    try_files $uri /wp-content/oxpulse-img.php?k=$2;
}

Note: the location regex uses [0-9a-f]+ (not a {16} quantifier) — an unquoted regex containing { / } makes nginx parse the braces as a block and fail to load (pcre2_compile() failed). Battle-tested plugins (WebP Express, Converter for Media) avoid the quantifier for this reason.

Security note (cache-dir hardening)

The .htaccess generated by the plugin includes php_flag engine off as defense-in-depth for Apache + mod_php hosts, guarded by <IfModule mod_php*.c> so it only applies when mod_php is actually loaded. A bare php_flag is a mod_php-only directive; under Apache + php-fpm (mod_proxy_fcgi / SetHandler — the modern default) with AllowOverride All it is an unknown directive and Apache returns 500 on every request served from the cache dir, so the guard is required. RemoveHandler/RemoveType are emitted unguarded (valid regardless of the PHP SAPI), and nginx does not read .htaccess at all. It is not the security boundary. The real guard is the signed-key + format-allowlist invariant in the miss-endpoint: the cache directory only ever contains <key>.webp files where key is HMAC-signed and the format extension is allowlisted to webp. An attacker cannot plant an executable-named file (e.g. <key>.php) — the signature check rejects unknown keys and the format allowlist rejects non-webp extensions with a 400 before any disk write. nginx hosts should additionally deny PHP execution inside the cache location block as belt-and-braces.

License

GPL-2.0-or-later

Copyright (c) 2026 Anatoly Koptev

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v0.1.10 Aug 18, 2026 oxpulse-imager.zip 1
v0.1.9 Aug 18, 2026 oxpulse-imager.zip 0
v0.1.8 Aug 18, 2026 oxpulse-imager.zip 0
v0.1.7 Aug 18, 2026 oxpulse-imager.zip 0
v0.1.6 Aug 18, 2026 oxpulse-imager.zip 0
v0.1.5 Jul 23, 2026 oxpulse-imager.zip 0
v0.1.4 Jul 23, 2026 oxpulse-imager.zip 0
v0.1.3 Jul 23, 2026 oxpulse-imager.zip 1
v0.1.2 Jul 22, 2026 oxpulse-imager-0.1.2.zip 0
v0.1.1 Jul 22, 2026 oxpulse-imager-0.1.1.zip 0