Developer information for extending and diagnosing Medifence from your theme or plugin.
Public API (the medifence_allow_access filter)
The final access decision can be overridden with the medifence_allow_access filter. It is applied after the default checks (logged in → holds the configured capability → carries a valid share token), so every decision can be changed.
add_filter( 'medifence_allow_access', function ( $allowed, $file_path ) {
// Example: restrict access to a specific role.
if ( ! current_user_can( 'edit_posts' ) ) {
return false;
}
return $allowed;
}, 10, 2 );
$allowed is the result of the default checks, and $file_path is the realpath()-resolved absolute path (the filter is only called for requests that passed path validation). The return value becomes the final decision. For files outside the protection scope (excluded paths or extensions) the filter is not called at all, because they are served before the authorization step.
How share links are signed
A share link is the file’s normal URL plus two query arguments:
mf_expires— the expiry as a Unix timestampmf_token—hash_hmac( 'sha256', <path relative to uploads> . '|' . <mf_expires>, <signing key> )
Because the signature covers both the file path and the expiry, a token cannot be reused for another file, nor can the deadline be extended by editing the URL. Comparison uses hash_equals() to avoid timing attacks. The signing key is stored in the database and replaced by “Revoke all share links”, which invalidates every issued link at once.
WP-CLI commands
wp medifence status [--fresh] # Report the protection status (exit 1 when unprotected)
wp medifence rewrite # Write the .htaccess rules
wp medifence remove # Remove the rules, disabling the protection
wp medifence verify # Run the same check and repair as the daily job
wp medifence share <id> [--days=N] # Create a share link and print the URL
status exits with a non-zero status when the protection is not working, so a deployment script or CI job can verify it mechanically. --fresh ignores the five-minute cache of the live test.
Diagnostic header (X-Medifence)
Responses denied by Medifence carry X-Medifence: denied; responses serving the placeholder image carry X-Medifence: placeholder. A response without this header did not pass through Medifence at all — useful for telling excluded paths and extensions, missing rules, a disabled mod_rewrite, and upstream caches apart.
.htaccess rules
The rules are written into the uploads .htaccess as a Medifence marker block; content from other plugins is preserved. In order: deny serving the .htaccess file itself → serve excluded extensions and excluded paths directly → rewrite requests for existing files to the site’s index.php?mf_file= (in the second mode, only the listed paths are rewritten).
WordPress bootstraps normally and the plugin intercepts the request on the init hook (priority 0) to authenticate and stream the file. No WordPress core file is ever loaded directly. Deactivating or uninstalling removes the block and files are served normally again. The full generated rules are shown at the bottom of the settings screen.
Delivery specification
- ETag / 304 — an ETag is derived from the path, modification time, and size; conditional requests receive an empty 304. Logged-in users’ browsers cache files locally, so the PHP delivery overhead is not paid repeatedly.
- Range support (206 / 416) — partial requests required for video and audio seeking are supported.
Cache-Control: private— storage in shared caches (CDNs, proxies) is forbidden. This is a fixed part of the design: it prevents files served to authenticated users from leaking to anonymous visitors.- Content-Disposition — only images, video, audio, PDF, and text are displayed inline; everything else is treated as a download (combined with
X-Content-Type-Options: nosniff). - Streaming — files are streamed in 8KB chunks, so large files never exhaust memory.
External services
The plugin never contacts any external service. The live test on the settings screen sends exactly one HTTP request — to your own site.