WP Manifestindependent plugin directory

Install

Download lucid-edge-cache.zip From the v0.10.5 release on GitHub, Oct 5, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/clicki-digital/lucid-edge-cache/releases/download/v0.10.5/lucid-edge-cache.zip
Updates itself. It ships its own updater (Update URI header), so new versions appear under Dashboard → Updates.

Lucid Edge Cache is Clicki Digital's WordPress full-page caching plugin. It stores safe anonymous HTML locally, coordinates Varnish purging and optionally replicates generated HTML to DigitalOcean Spaces.

The plugin deliberately bypasses logged-in sessions, dynamic WordPress views, query strings, private content and common WooCommerce session state.

Installation

Install the lucid-edge-cache.zip asset from the latest stable release through WordPress. Version 0.7.0 is the one-time updater bootstrap; installations older than 0.7.0 must upload it manually once.

Infrastructure onboarding writes protected values to wp-config.php rather than storing credentials in the WordPress database.

Releases

Pushing a semantic version tag such as v0.7.0 runs the release workflow. It checks version consistency and PHP syntax, builds the canonical WordPress ZIP, generates its SHA-256 file and publishes both files to GitHub Releases.

See RELEASING.md for the complete release and staged rollout checklist.

Releases

19 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v0.10.5 latest
Oct 5, 2026 27d ago
lucid-edge-cache.zip +1 asset
18
lucid-edge-cache.zip18
lucid-edge-cache.zip.sha2560
Oct 5, 2026 27d ago
lucid-edge-cache.zip +1 asset
1
lucid-edge-cache.zip1
lucid-edge-cache.zip.sha2560
Oct 5, 2026 27d ago
lucid-edge-cache.zip +1 asset cohort
2
lucid-edge-cache.zip2
lucid-edge-cache.zip.sha2560
Oct 5, 2026 27d ago
lucid-edge-cache.zip +1 asset
2
lucid-edge-cache.zip2
lucid-edge-cache.zip.sha2560
Oct 1, 2026 1mo ago
lucid-edge-cache.zip +1 asset
12
lucid-edge-cache.zip12
lucid-edge-cache.zip.sha2560
Oct 1, 2026 1mo ago
lucid-edge-cache.zip +1 asset
2
lucid-edge-cache.zip2
lucid-edge-cache.zip.sha2560
Sep 28, 2026 1mo ago
lucid-edge-cache.zip +1 asset
3
lucid-edge-cache.zip2
lucid-edge-cache.zip.sha2561
Sep 28, 2026 1mo ago
lucid-edge-cache.zip +1 asset
1
lucid-edge-cache.zip1
lucid-edge-cache.zip.sha2560
Sep 28, 2026 1mo ago
lucid-edge-cache.zip +1 asset
3
lucid-edge-cache.zip3
lucid-edge-cache.zip.sha2560
Sep 28, 2026 1mo ago
lucid-edge-cache.zip +1 asset
0
lucid-edge-cache.zip0
lucid-edge-cache.zip.sha2560
Sep 28, 2026 1mo ago
lucid-edge-cache.zip +1 asset
1
lucid-edge-cache.zip1
lucid-edge-cache.zip.sha2560
Sep 20, 2026 1mo ago
lucid-edge-cache.zip +1 asset
4
lucid-edge-cache.zip4
lucid-edge-cache.zip.sha2560
Sep 20, 2026 1mo ago
lucid-edge-cache.zip +1 asset
1
lucid-edge-cache.zip1
lucid-edge-cache.zip.sha2560
Sep 20, 2026 1mo ago
lucid-edge-cache.zip +1 asset
2
lucid-edge-cache.zip2
lucid-edge-cache.zip.sha2560
Sep 20, 2026 1mo ago
lucid-edge-cache.zip +1 asset
0
lucid-edge-cache.zip0
lucid-edge-cache.zip.sha2560
Sep 20, 2026 1mo ago
lucid-edge-cache.zip +1 asset
1
lucid-edge-cache.zip1
lucid-edge-cache.zip.sha2560
Sep 15, 2026 1mo ago
lucid-edge-cache.zip +1 asset
3
lucid-edge-cache.zip2
lucid-edge-cache.zip.sha2561
Sep 15, 2026 1mo ago
lucid-edge-cache.zip +1 asset
2
lucid-edge-cache.zip1
lucid-edge-cache.zip.sha2561
Sep 15, 2026 1mo ago
lucid-edge-cache.zip +1 asset
5
lucid-edge-cache.zip3
lucid-edge-cache.zip.sha2562
D+ grade

Security

Lucid Edge Cache 0.10.5 · audited by WP Registry

High-severity findings.

1 high 2 medium
Audited release
0.10.5
Findings
3
Worst severity
high
Content hash
69501946e07d2ead9dbc72a5…

Findings

  • high Spoofable preload header stores query-string HTML as the public page

    missing_auth

    An unauthenticated visitor can poison the anonymous page cache. LEC_Cache::bypass_reason() skips the query-string bypass whenever the request merely contains an X-Lucid-Cache-Preload header. That header is not a secret: the preloader sends the static value 1, and any client can do the same. start_capture() then buffers the response, and store_response() writes the body to a path built only from the URL path (relative_cache_path uses PHP_URL_PATH), ignoring the query string. The drop-in refuses to serve a request that has a query string, then the next normal GET is a HIT and readfile() sends the stored body as text/html to every logged-out visitor for the TTL (default 6 hours). A concrete case is a WooCommerce shop or category URL with orderby, min_price, or attribute filters: that variant replaces the canonical page. If the origin reflects the query string into the HTML, the reflection is stored and the browser runs it. A front-end URL that shows private data for a query token without Set-Cookie is likewise stored and replayed. When Spaces constants are set, settings() forces spaces_enabled and the same body is PUT with x-amz-acl public-read. store_response() never reads the enabled flag, so Emergency disable (which only makes the drop-in return early and deletes existing files) does not stop a later poisoned write or the public Spaces upload. Logged-in users, admin, ajax, cron, 404s, feeds, and responses that Set-Cookie are still excluded.

    includes/class-lec-cache.php:214-215

    Recommendation

    Do not treat X-Lucid-Cache-Preload as authorization. Keep rejecting every query string except a single cache-buster whose value is a site secret (hash_equals against a key that is not the literal 1), and still key the file on the path with that argument removed. Never store a response that carried any other query parameter. Check the enabled flag in start_capture() before buffering, and skip the Spaces upload when page caching is disabled.

  • medium Drop-in cache path keeps .. and writes outside the cache directory

    file_access

    An unauthenticated GET or HEAD with no query string reaches dropins/advanced-cache.php before WordPress routing, as long as page caching is enabled and the visitor sends no protected cookie. The path is rawurldecode()'d and each segment is filtered with a character class that still allows dots, then trim(..., '-') which does not remove dots, so a segment of .. or %2e%2e survives. The resolved file is cache_dir/host/<segments>/index.html with no realpath() check that it stays under cache_dir. When that file is missing or older than the TTL, the drop-in mkdir()s dirname($file) with mode 0750 and fopen()s index.html.lock in mode c, which creates directories and an empty lock file anywhere the PHP user can write. If a fresh index.html already exists at the traversed path, readfile() sends it to the attacker as text/html. The same segment filter is duplicated in LEC_Cache::relative_cache_path() and used by store_response(); a traversal URL that WordPress still answers with HTTP 200 HTML would also write that HTML outside the cache root. Typical nginx php-fpm setups pass the raw REQUEST_URI through to index.php for unknown paths, so %2e%2e is decoded by this file rather than by the web server. The filename is fixed (index.html or index.html.lock) and the lock body is empty, so this is not arbitrary PHP write.

    dropins/advanced-cache.php:29-48

    Recommendation

    Drop any segment that is empty, `.`, or `..` after decoding, and reject the request if realpath(dirname($file)) is not inside realpath(cache_dir). Use that helper for both the drop-in and LEC_Cache::relative_cache_path() before mkdir, fopen, readfile, or file_put_contents. Do not rawurldecode a client path into a filesystem suffix without a prefix check.

  • medium Unauthenticated admin-ajax request rewrites wp-config.php to force WP_CACHE

    missing_capability

    LEC_Admin::boot() runs whenever is_admin() is true, which includes unauthenticated /wp-admin/admin-ajax.php because admin-ajax.php defines WP_ADMIN before wp-load.php. It hooks ensure_prerequisites on admin_init with no capability check, and admin.php fires admin_init before the ajax action is authenticated. If WP_CACHE is not already true, ensure_prerequisites() calls LEC_Config::ensure_wp_cache(), which removes recognised WP_CACHE lines and writes a hardcoded define('WP_CACHE', true) into wp-config.php. A subscriber loading profile.php does the same. The observable consequence is that an operator who set WP_CACHE to false (so WordPress will not load advanced-cache.php) has that control turned back on by an anonymous request, and the drop-in starts serving and capturing HTML again. The written line is not attacker-controlled, and a file that already contains exactly one enabled definition is left unchanged. Separately, LEC_Cache::maybe_upgrade() is also on admin_init with no capability check: while the lec_version option differs from LEC_VERSION, the first admin-ajax hit installs the bundled drop-in, rewrites the runtime config, and runs the same wp-config normalisation. After the version option matches, that path returns immediately.

    includes/class-lec-admin.php:37-45

    Recommendation

    Run ensure_prerequisites() and maybe_upgrade() only for a user with manage_options, and return immediately when wp_doing_ajax() or the user is logged out. Do not rewrite wp-config.php from admin-ajax.php. If WP_CACHE is explicitly false, leave it alone instead of retrying on every administration request.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.