Description
By default, every file inside the WordPress uploads directory can be opened by anyone who knows its URL. For membership sites this means that protected content pages are useless if the images inside them can still be opened directly.
Medifence puts a login check in front of every uploads request — without changing a single URL and without moving any files.
How it works
On activation, the plugin writes a small rule block into the .htaccess file inside your uploads directory (using WordPress’s own marker mechanism, so rules from other plugins are preserved). Requests for existing files are rewritten to WordPress itself, which bootstraps normally; the plugin then verifies the visitor is logged in and streams the file. No WordPress core file is ever loaded directly. Deactivating or uninstalling the plugin removes the rules, and your files are served normally again.
Features
- File URLs stay exactly the same — nothing to update in posts or the database
- Physical files stay where they are — no migration needed
- Built-in live verification: the settings screen requests a test file without a session cookie and shows the actual HTTP status anonymous visitors receive
- A warning appears on all admin screens if another plugin overwrites the rules
- Optionally require a specific capability in addition to being logged in
- Or give individual folders their own capability, so one membership tier cannot reach another’s files
- Choose the denied status code (403, 404, or 401) — 404 hides that the file exists
- Or send visitors who open a protected file to the login screen, and back to the file afterwards
- Or to a page of your own — a membership or contact page, for people who have no account yet
- Optionally serve a placeholder image instead of denied images
- Exclude specific file extensions (useful when your theme loads CSS or fonts from uploads)
- Excluding a document or archive type — pdf, zip, docx — is flagged before and after saving, so it never happens unnoticed
- Exclude individual folders, or flip it around and protect only the folders you list — picked from the folders you actually have
- Or let each file follow the post it is attached to: published posts stay public, private ones require a login
- Publish the generated image sizes a gallery needs while the full-resolution original stays protected
- WebP and AVIF copies made by an image optimizer follow the file they were made from — published size or protected original
- Make a single file an exception to all of that — always public, or always protected — from the media library
- A shortcode lists the files of a folder that the current visitor may download — the member page that never drifts out of date
- Share a single protected file with someone who has no account, using an expiring signed link — and revoke any of them individually
- Limit a share link to a number of downloads, note down who it was issued to, and see how often it has been used
- Give a share link a password as a second factor — only its hash is stored, and guessing is cut off after 20 wrong attempts
- Or select a set of files in the media library and get all their links in one go
- Set the lifetime and download limit the share forms start with, for sites that issue many links the same way
- Send a freshly issued link by email straight from the result box — no pasting, no misaddressing
- Download the ledger of active links as CSV — without the links themselves, so a leaked ledger leaks no access
- Pause the protection for a few minutes while diagnosing a problem — it always resumes by itself, and the rules stay in place
- A daily check restores the rules automatically if another plugin removes or overwrites them
- What was overwritten is kept for you to inspect — the clue to which plugin keeps doing it
- Repeated overwrites are called out as a standing conflict, instead of being silently repaired forever
- Optionally emails you when the protection stops working — once, when the verdict changes
- And when the number of files anyone can open grows, which a working set of rules will never tell you
- That number is kept as a 30-day trend, so a scope drifting wider over weeks is visible at a glance
- The files that became reachable since the previous count are named, not just counted
- Or get one summary a week — reachable files, blocked requests, active links, settings changes — even when nothing happened
- Reports the protection status in Site Health, and offers WP-CLI commands for deployments
- A dashboard widget puts the vital signs on the admin home, seen without anyone going looking
- Warns when WP-Cron is not running — the silent way every scheduled check, alert, and summary stops
- Inspect any single file to see whether it is reachable, and which setting decided it
- Preview a settings change before saving it: see exactly which files would stop being protected
- Carry a configuration from staging to production as a JSON file, reviewed before it is applied
- See which administrator changed the protection, when, and from what to what — and undo a recorded change with one click
- Audit the whole library in one click and get the list of files your settings leave public — and close them from that same list
- Scan the uploads directory itself for files no attachment points at — the ones nothing else reports
- Scan published content for embedded protected files — the setting that shows up as a broken image on a public page
- The audit also names the attachments whose file is missing on disk, before someone issues a share link that cannot work
- Optionally count what was blocked, per file — without storing IP addresses or user agents — and download the list as CSV
- Choose how many files the log keeps and for how long, and see the last 7 days as one count per day
- See at a glance which files are protected, from a column in the media library
- The same verdict appears in the file dialog while inserting media into a post, and as a read-only REST field for administrators
- Range request support for video and audio seeking, ETag-based browser caching for logged-in users
- Lock the configuration with a wp-config.php constant, for sites where the protection scope is agreed rather than casual
- Cleans up completely on uninstall
Requirements
- Apache or LiteSpeed with mod_rewrite enabled
- A writable
.htaccessfile in the uploads directory
This plugin does not work on nginx, because nginx does not read .htaccess files. The built-in live test will tell you immediately whether your server is compatible.
For developers
The final access decision can be filtered:
add_filter( 'medifence_allow_access', function ( $allowed, $file_path ) {
// Example: always allow files inside uploads/public/.
if ( false !== strpos( $file_path, '/uploads/public/' ) ) {
return true;
}
return $allowed;
}, 10, 2 );
Screenshots






Installation
- Upload the
medifencefolder to/wp-content/plugins/, or install it from the Plugins screen. - Activate the plugin. The
.htaccessrules are written automatically. - Go to Media Access Control and confirm that the live test reports “Anonymous access is blocked”.
- Optionally, open a media file URL in a private browsing window to verify it yourself.
FAQ
-
Do my file URLs change?
-
No. Existing URLs in posts, themes, and the database keep working exactly as before. Logged-in visitors see no difference at all.
-
How do I know the protection is actually working?
-
The settings screen runs a live test: it creates a temporary file, requests it over HTTP without any session cookie, deletes it, and reports the status code. If your server configuration prevents the rules from working, you will see it there — not months later.
-
Does it work on nginx?
-
No. The plugin relies on
.htaccessrewrite rules, which nginx does not support. You would need an equivalent rule in your nginx configuration, which is outside what a plugin can safely manage. -
What happens when I deactivate the plugin?
-
The rules are removed from the
.htaccessfile and all files are served normally again. Uninstalling additionally deletes the plugin’s settings. -
Another plugin overwrote the .htaccess file. What now?
-
Medifence detects this and shows a warning on your admin screens. One click on “Regenerate .htaccess rules” restores them. A daily scheduled check also rewrites the rules on its own, so the protection comes back even if nobody logs in; you can turn that off in the settings.
-
Will I hear about it if the protection breaks while nobody is logged in?
-
Switch on the email notification in the settings. The daily check then sends one message when it finds that anonymous visitors can reach your files, and one more when the protection works again. It is sent only when the verdict changes, so a problem that persists does not fill your inbox, and a live test that could not reach the site at all is never reported as a failure. Use “Send a test email” first to confirm that this site can send mail at all.
-
Will I hear about it if someone widens the protection scope?
-
Yes, if the email notification is on. The live test answers “do the rules work”, and a widened scope leaves that answer green — the rules work exactly as configured, and it is the configuration that changed. So the daily check also counts how many files a logged-out visitor can open, and writes to you when that number grows. A number that falls is never reported: locking more files down is not news.
The message names a few of the files and links to the Audit tab, which lists all of them with the reason for each. The settings tab records which administrator last changed the protection. The daily count examines the newest 500 attachments; the
medifence_exposure_limitfilter changes that. -
Does this modify files outside the plugin directory?
-
Yes, one file: the
.htaccessinside your uploads directory. The plugin uses WordPress’sinsert_with_markers()function, which only touches its own marked block and preserves everything else. The block is removed on deactivation and uninstall. -
Does the plugin contact any external service?
-
No. The live test sends one HTTP request to your own site. Nothing is sent anywhere else.
-
Does the plugin store personal data?
-
Nothing about your visitors. The optional access log records the file path, what happened to it, and when — no IP addresses, no user agents, and no user names. It is off unless you switch it on, keeps at most 200 files for 30 days, and can be cleared with one click.
The settings history is the one place a person is named, and only an administrator of your own site: when the settings that decide access change, it records which user account made the change. That is the point of it — a protection that was widened should be traceable. It keeps the last 50 changes and can be cleared with one click.
-
How do I find out which files my visitors are actually blocked from?
-
Switch on the access log in the settings, then look at the Access log tab. It counts per file what Medifence did: blocked the request, served the placeholder, sent the visitor to the login screen, or accepted a share link. A file that is blocked over and over is usually one your theme or another plugin needs for everyone — exclude its extension or its folder.
The list can be downloaded as CSV from the same tab — one row per file, with one column per event — or produced on the command line with
wp medifence log --format=csv. -
Does this slow down my site?
-
Protected files are served through PHP, which adds overhead compared to direct file delivery. To keep this small, the plugin sends ETag headers so browsers of logged-in users cache files locally, and repeat requests are answered with an empty 304 response. Files with excluded extensions bypass PHP entirely.
-
A file is still reachable right after I enabled the protection. Why?
-
Files that were served before the protection was active — or while their folder or extension was excluded — may still sit in a server-side or CDN cache, and that copy is served without ever reaching WordPress. Medifence sends
Cache-Control: privateon everything it delivers itself, so protected files are never stored in a shared cache; the stale copy disappears once the cache entry expires, or immediately if you purge the cache. Verify with a fresh query string (for example?x=1), which bypasses most caches. -
My site has several membership tiers. Can gold files be closed to silver members?
-
Yes. Put each tier’s files in its own folder and give that folder its own capability under “Folders that need more than a login” on the settings tab. A logged-in visitor without that capability is refused exactly as a logged-out one is.
The most specific folder decides, so a rule on
members/goldis applied ahead of one onmembers. Folder rules can only narrow access — they never let in someone who already failed the site-wide capability — and a share link still works for the one file it was issued for. The file inspector names the capability a given file requires, and which roles have it. -
Can I keep the images in published posts public, but protect the ones in members-only posts?
-
Yes. Choose the “Follow the post each file is attached to” scope. A file attached to a published post is served to everyone, exactly as before the plugin was active; a file attached to a private, draft, or password-protected post requires a login. Files that belong to no post remain protected, and generated image sizes follow their original — a thumbnail is never more public than the full image.
-
Can visitors see the thumbnails while only members can open the full-resolution photo?
-
Yes. Tick the generated sizes that may be public under “Image sizes anyone may see” on the settings tab. Everything else inside the protected scope, including the original file and the
-scaledcopy WordPress makes of a large upload, keeps requiring a login.Which size a file is comes from what WordPress recorded when it generated it, not from the numbers in the file name, so a size that does not crop cannot slip through a coincidental
-1024x1024match. A file whose size WordPress has no record of is treated as protected.This can only open files the scope would otherwise protect. An excluded extension or folder is decided first — those files are delivered by your web server and are public regardless — and a per-file exception still overrides everything. A published size is still delivered by WordPress, so it is no faster than a protected file.
-
Someone clicks a protected PDF and just gets an error. Can I show the login screen instead?
-
Yes. Set “When access is denied” to “Send visitors to the login screen, then back to the file”. After logging in they land on the file they originally asked for. The redirect applies only when the browser navigates to the file directly — an
<img>or a video embedded in a page still receives the status code, so your pages never end up showing a login form where an image should be. -
A page is broken and I suspect the plugin. Do I have to deactivate it to check?
-
No. Use “Pause the protection” on the Protection status tab: every file is served as if the plugin were inactive, but the
.htaccessrules stay in place and the protection resumes automatically after the time you chose — 5 minutes up to 1 hour, so it cannot stay off by accident. While the pause lasts, responses carry anX-Medifence: pausedheader, a warning shows on every admin screen, and the email notification does not report the pause as a failure. Also available aswp medifence pauseandwp medifence resume. -
What about files in the uploads directory that are not in the media library?
-
They are protected by the same rules — the scope works on paths, not on the database — but until now nothing reported on them, because the audit walks the media library. “Scan the uploads directory” on the Audit tab walks the directory instead and lists the files no attachment points at which a logged-out visitor can still open: an export another plugin left behind, a leftover from a migration, something copied in over FTP.
Generated image sizes, the scaled copy of a large upload, and edited versions are resolved back to their original, so they are not reported as strays. Also available as
wp medifence stray. -
Can I set this up on staging and copy the configuration to the live site?
-
Yes. “Download the settings” on the settings tab writes a JSON file, and “Read a settings file” on the other site reads it back. Only the settings travel — share links, the signing key, the access log, and per-file exceptions belong to one site and are never exported.
An imported file is not applied on the spot. It fills in the form as an unsaved change and reports what it would do to that library, so a scope that made sense on staging cannot quietly expose files on the live site. Press save once the effect looks right. On the command line,
wp medifence export --file=medifence.jsonandwp medifence import medifence.jsondo the same, and--dry-runreports the effect without saving. -
I want to change the protection scope, but I am afraid of exposing something.
-
Use “Preview the effect” next to the save button. It applies the values currently in the form to your media library without saving them, and reports which files would change side — the ones that would stop being protected first, since that is the direction that matters. Your edits stay in the form, so you can then save them or discard them. Nothing is written until you press save.
-
The login screen is the wrong answer for people who have no account yet.
-
Set “When access is denied” to “Send visitors to a page of your own” and pick a published page — a membership page, a contact form, whatever explains how to get access. The file they tried to open is added to the URL as
mf_from, so the page can name it (“You tried to open report.pdf“).The same rule as the login redirect applies: it only happens when someone opens the file in their browser. An image or a video embedded in a page still receives the status code, so your pages never show a membership page where a picture should be. If the chosen page is later unpublished or trashed, denied visitors quietly fall back to the status code rather than being sent into a 404.
-
The audit found files anyone can open. How do I close them?
-
From the list itself. Tick the ones that should not be open and press “Set the selected files to always protected” — that stores a per-file exception without touching the protection scope, so nothing else moves. The library is scanned again straight afterwards, so whatever is still listed is still reachable.
Some files cannot be closed this way, and are reported separately. A per-file exception is decided in PHP, so it only works for files the rules actually route through WordPress. A file your web server delivers itself — because its extension is excluded, its folder is excluded, or the scope is limited to other paths — never reaches the plugin at all, and only a change to the scope closes it.
-
A file is public when I expected it to be protected. How do I find out why?
-
Use the file inspector on the Protection status tab, or the Audit tab to see all of them at once — it applies your settings to the whole library and lists every file a logged-out visitor can open, with the reason for each.
For a single file: Paste the file’s URL (or enter its attachment ID or path) and it reports whether a logged-out visitor can reach it, naming the setting that decided — an excluded extension, an excluded path, a scope that does not cover it, or the status of the post it is attached to. The same check is available from the command line as
wp medifence inspect <file>. -
One single file has to be public (or protected), but the rest of the folder should stay as it is.
-
Select the file in the media library and use the bulk action “Medifence: always public” or “Medifence: always protected”; the same choice is on the file’s own edit screen. That one file then ignores the protection scope, and generated image sizes follow their original. Every exception is listed on the Audit tab and can be reset there.
One limit is worth knowing: “always protected” only works for files that reach Medifence at all. If the file has an excluded extension, or sits in an excluded folder, your web server delivers it directly and no plugin is asked. The file inspector says so when it happens.
-
Can I let someone without an account see one file?
-
Yes. On the settings screen, or from the “Share link” action in the media library, create a share link for that file and choose how long it stays valid. The link is the file’s normal URL plus a signature, so it works for that one file only and stops working when it expires.
You can also limit a link to a number of downloads — one, for a file that should be fetched exactly once — and add a note recording who it was issued to. Revalidating a cached file and seeking inside a video are not counted, so a one-download link is not spent halfway through a video.
Every active link is listed on the settings screen with its expiry, how often it was downloaded, and when it was last used; each one can be revoked on its own. “Revoke all share links” additionally replaces the signing key, invalidating everything issued so far at once.
-
Can I send someone a whole set of files?
-
Select them in the media library and choose the bulk action “Medifence: create share links”. The lifetime, the download limit, and the note are asked once and applied to every link — so a note like the client’s name ends up on all of them — and the links are then shown together, with a box holding all of them one per line for copying into a single message.
Each link still covers its own file alone, and the download limit applies to each link separately rather than to the set. Up to 25 files per request. Files whose file is missing on disk are named rather than silently dropped.
Reviews
There are no reviews for this plugin.
Contributors & Developers
“Medifence – Access Control for Media Uploads” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “Medifence – Access Control for Media Uploads” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
1.53.1
- The screenshots on this page were taken again: the screens they showed date from 1.17.0, before the audit, the scheduled checks, the change history, and most of the share link options existed. Six screens now, in place of four. No change to the plugin itself.
1.53.0
- The list of active share links can now be downloaded as CSV: file, attachment, issue and expiry dates, download counts, whether a password is set, and the note — the ledger a compliance process asks for.
- The links themselves are deliberately not in the file. A ledger gets attached to emails and dropped into shared folders; leaking it must not leak access.
- Times are machine-readable UTC, matching the access log export.
1.52.0
- A freshly issued share link can now be sent by email straight from the result box: recipient, an optional line, one press. The message carries the link, the file name, and the expiry.
- Copy-and-paste into a mail client is where links get truncated and addressed to the wrong person; sending from here removes the paste. Each press sends exactly one message — nothing is automatic.
- Nothing about the recipient is stored. Only a link whose record is still active can be sent, and only links of this site — the form cannot be turned into a relay for arbitrary URLs.
1.51.0
- The lifetime and download limit that the share forms start with can now be chosen under “Share link defaults” on the settings tab. A site that issues many links the same way stops re-selecting the same values every time.
- Only the preselection changes: every link can still be issued with different values, links already issued are untouched, and WP-CLI keeps its own defaults so scripts stay reproducible.
1.50.0
- A dashboard widget now shows the protection’s vital signs on the admin home: whether the rules are in place, the last live test, how many files anyone can open, the active share links, and whether the scheduled checks run.
- The status tab and Site Health answer when someone goes looking; the dashboard is what gets seen without looking. Administrators only.
- The widget reads stored values exclusively — rendering it never scans the library and never makes an HTTP request.
1.49.0
- The daily count now remembers which files it saw, so the files that became reachable since the previous count are named — on the Audit tab and in the increase email — instead of leaving an administrator to find them by hand.
- Stored as hashes with a hard cap; when the library outgrows what the daily count examines, the comparison says so rather than quietly claiming completeness.
- The first count only records the baseline. Nothing is reported until there are two counts to compare.
1.48.0
- The daily count of reachable media files now runs with the daily check regardless of the email notification, and the last 30 days are kept as a trend on the status tab.
- A single day’s comparison catches a jump; only a trend catches the quiet drift of a scope widening over weeks. Days the check did not run are shown as absent, not as zero — an uncounted day is unknown, not empty.
- The increase email itself still requires the notification switch, exactly as before.
1.47.0
- Repairs of the rule block are now counted, and two or more within a week are called out on the status tab and in Site Health as a standing conflict with another plugin.
- One overwrite is an accident the automatic repair already handles. A repeated one means something on the site keeps writing over the rules — and a conflict that gets silently repaired forever is a conflict nobody ever fixes.
- The warning points at the kept snapshot of what the other plugin wrote, which is the clue to which one it is. Reading the verdict costs nothing: it is judged from stored timestamps, never by scanning.
1.46.0
- New: the MEDIFENCE_LOCK_SETTINGS constant in wp-config.php freezes everything that moves the protection scope — saving, previewing, importing, undoing from the history, and per-file exceptions, in the admin and over WP-CLI alike.
- Diagnosis, audits, the access log, share links, and the pause stay available: the lock freezes the scope, not the operations. The settings tab says plainly that it is locked.
- This is the written-down form of an agreement, not a security layer — whoever can edit wp-config.php can remove the constant. For handed-over sites and teams where the protection is not changed casually.
1.45.0
- Site Health now carries two more Medifence tests: a recommendation when document or archive extensions are excluded from the protection, and the last daily count of reachable media files.
- Both read stored values only — opening Site Health never triggers a scan or an HTTP request.
- The exclusion warning matches the one on the settings screen; the count is information rather than a verdict, with a link to the audit that lists each file.
1.44.0
wp medifence status --format=jsonandwp medifence inspect --format=jsonprint the full report as one JSON object, for deployment scripts and CI that so far had to parse the human-readable table.- The exit codes are unchanged — a pipeline that only asks “is the protection working” keeps its one-line answer, and one that wants the details no longer greps for them.
auditandstrayalready offered machine-readable listings; their output is untouched.
1.43.0
- WebP and AVIF copies placed beside the originals by an image optimizer (foo.jpg.webp, foo-300×200.webp) are now resolved back to the file they were made from. Optimizers record these copies nowhere, so until now every attachment-based decision saw them as unknown files.
- They follow their source everywhere: the derivative of a published image size is public, the derivative of the protected original stays protected, and the “follow the parent post” mode treats them as the post’s own image.
- A replaced-extension derivative whose source is ambiguous — foo.jpg and foo.png existing as different attachments — stays unresolved on purpose, and unresolved falls to the protected side.
1.42.0
- Repairing the rules — automatically or with the rewrite button — now keeps what stood in the rule block beforehand. Repairing used to destroy the evidence: whatever another plugin wrote over the rules was gone the moment they were restored, and with it the only clue to who keeps overwriting them.
- The settings tab shows the kept content next to the last-repair line, or says plainly that the rules were removed rather than overwritten.
- One generation is kept, and only the marker block — the rest of the file is other plugins’ business.
1.41.0
- A recorded settings change can now be undone with one click from the history: the previous values of that entry are restored, the rules are rewritten, and the undo itself appears in the history.
- Recovering from a wrong save used to mean reading the old values out of the history and typing them back in. Now it is the button next to the entry.
- Only the settings that entry changed are touched, and everything runs through the normal save path — nothing downstream is skipped. Entries recorded by versions before this one are shown but cannot be undone, because the exact previous values were not stored yet.
1.40.0
- The audit now lists the attachments whose file is missing on disk — deleted over FTP, or lost in a migration — with the recorded path, a link to the attachment, and the upload date. The count existed before; which files it meant did not.
- The uploads scan finds files without an attachment; this is the reverse: attachments without a file. Kept separate from the reachable-files list, because a missing file is an integrity problem, not an access problem.
- Requests for such files are answered with 404 either way; the point is to repair or retire them before someone issues a share link that cannot work.
1.39.0
- The status tab and Site Health now report when the scheduled checks are overdue. The automatic repair, the alert emails, and the weekly summary all ride on WP-Cron — and on a site with almost no visits, or with DISABLE_WP_CRON set but no server cron job, all of it stops silently while the protection itself keeps working.
- An event more than a day past due is the direct evidence, so the verdict needs no extra bookkeeping and is judged when an admin screen asks — a cron cannot report its own absence.
- The status tab also shows when the daily check last actually ran.
1.38.0
- New shortcode:
[medifence_files path="members/gold"]lists the files of that uploads folder — but only the ones the visitor viewing the page could actually download, decided by the same rules as the delivery itself. - A member sees their documents, a visitor without access sees nothing, and the list can never disagree with what the protection would serve — which a hand-maintained list always eventually does.
- The output is one unstyled list with class names, for the theme to style. The plugin sets DONOTCACHEPAGE on pages using it; make sure your caching plugin honours that.
1.37.0
- The Audit tab can now scan published content and list the protected files it links to — the opposite question from the other scans: not “what can anyone open” but “what did we embed that visitors cannot open”.
- A protected file inside a published post is the misconfiguration that shows up as a broken image or an inaccessible link on a public page, and until now nothing reported it.
- The verdict mirrors the delivery path exactly: published image sizes, per-file exceptions, and the content-aware mode’s published-parent rule are all honoured, so every finding is one an anonymous visitor really cannot open.
1.36.0
- Excluding a document or archive extension — pdf, zip, doc, docx, xls, xlsx, ppt, pptx, csv, rar, 7z, gz, tar — is now flagged: a warning appears under the exclusion field, in the preview of an unsaved change, and in the message after saving.
- These are the files a protected uploads directory usually exists for, and an excluded extension covers every file of that type, including future uploads. The warning does not block anything — excluding them is sometimes right; doing it unnoticed never is.
1.35.0
- The file dialog — the one that opens when media is inserted into a post — now shows whether the file is protected or public, with the deciding rule underneath. A protected file is recognised before it lands in a public page.
- The media REST endpoint gains a matching read-only
medifencefield with the verdict, the reason, and a description. It is returned to administrators only; everyone else receives null — which files are reachable is itself information. - No JavaScript was added: the dialog field rides on WordPress’s own attachment details, so it works in the block editor’s media dialog and the classic one alike.
1.34.0
- How many files the access log keeps (50–500) and for how long (7–90 days) can now be chosen, next to the log switch. The defaults are unchanged: 200 files, 30 days.
- The log tab now shows the last 7 days as one count per day, in the site’s timezone, so a surge of blocked requests stands out without reading the list.
- The weekly summary names the configured limits instead of the old fixed ones.
1.33.0
- A weekly summary email can now be switched on: one message a week with the number of files anyone can open, what the access log counted, the active share links, and any changes to the protection settings.
- The existing alert only speaks when something changes; the summary confirms the quiet weeks too, and goes to the same notification address.
- “Send the weekly summary now” on the settings tab shows what it will look like without waiting a week.
1.32.0
- A share link can now carry a password, as a second factor against the link being forwarded or leaking: whoever opens it is asked for the password before the file is served.
- A correct answer is remembered in a signed cookie for an hour — at most until the link itself expires — so a video keeps seeking and a gallery keeps loading without asking again.
- Only a hash of the password is stored. After 20 wrong attempts the link stops accepting passwords for good; the issuer can always create a new link.
- The password is optional and set when the link is created — on the Share links tab, in the bulk action, or with
wp medifence share --password=....
1.31.0
- Deleting a file from the media library now also revokes its share links. They stopped working anyway once the file was gone, but their records stayed in the list until they expired, and a list that shows links nobody can use is not telling the truth.
1.30.0
- Generated image sizes can now be published while the original file stays protected — for a photo site where the gallery is open to everyone and the full resolution is not.
- Tick the sizes anyone may see under “Image sizes anyone may see” on the settings tab. The original and the
-scaledcopy of a large upload are never among them. - Which size a file is comes from what WordPress recorded when it generated it, not from the numbers in the file name, so a size that does not crop cannot leak through a coincidental match.
- This only widens access inside the protected scope: an excluded extension or folder is still decided first, and a per-file exception still overrides everything.
- Fixed: generated sizes of an image large enough to have been scaled on upload did not resolve back to their attachment, so the “follow the parent post” mode treated them as belonging to no post.
1.29.0
- The audit is no longer limited to the newest 1000 attachments: it examines a batch at a time and offers to continue, so a library of any size can be covered from the admin screen.
- It reports how far it has come and says plainly when the whole library has been examined — the oldest files are reached last, and those are the ones nobody remembers.
- Batches are paged by attachment ID rather than by offset, so files added or deleted while the scan runs cannot make it skip or repeat any.
wp medifence auditnow covers the whole library by default; pass--limitto stop early.
1.28.0
- Individual folders can now require their own capability, so a site with several membership tiers can keep each tier’s files closed to the others.
- The most specific folder decides: a rule on
members/goldis applied ahead of one onmembers. - Folder rules only narrow access — they never admit someone who failed the site-wide capability — and share links keep working for the file they were issued for.
- The settings screen reports which roles have each capability, and the file inspector names the rule a given file falls under.
1.27.0
- The audit’s list of files anyone can open is now actionable: select files and set them to “always protected” without leaving the list.
- The library is scanned again immediately afterwards, so what remains on screen is what is still reachable rather than what was reachable before.
- Files your web server delivers without ever asking the plugin — an excluded extension, an excluded folder, or a scope limited to other paths — are named separately, because storing an exception for those changes nothing, and saying otherwise would claim a protection that is not there.
1.26.0
- Share links can now be created for several files at once: select them in the media library and use the new bulk action.
- The lifetime, the download limit, and the note are asked once and applied to every link, and the links are shown together for copying into a single message.
- Each link still covers its own file alone, and the download limit applies per link. Up to 25 files per request.
- Files whose file is missing on disk are named rather than quietly left out of the set.
1.25.0
- The daily check now counts how many files anyone can open, and emails you when that number grows.
- This catches what the live test cannot: a widened scope leaves the rules working perfectly, so the existing verdict stays green while more files sit outside the protection.
- A falling number is never reported, and the first count is only a baseline, so switching the notification on does not produce a message.
- The message names a few of the files and links to the Audit tab. The count examines the newest 500 attachments; the new
medifence_exposure_limitfilter changes that.
1.24.0
- The Audit tab can now scan the uploads directory itself, not only the media library, and report the files no attachment points at that anyone can open.
- These are the files nothing else mentions — exports another plugin left behind, leftovers from a migration, anything copied in over FTP — because every other view starts from the library.
- Generated sizes, the scaled copy of a large upload, and edited versions are resolved back to their original, so they are not mistaken for strays.
- Also available as
wp medifence stray, which covers the whole directory and can write the list to CSV.
1.23.0
- The settings that decide access now keep a change history: what moved, from what to what, when, and which administrator did it.
- Covers every route into the settings — the form, an imported file, and WP-CLI — because the record is taken where the settings are written.
- Settings that move no file, such as the log switch or the notification address, are left out so the list stays readable.
- The last 50 changes are kept and can be cleared at any time. Also available as
wp medifence history, with--clear.
1.22.0
- Denied visitors can now be sent to a page of your own instead of the login screen — a membership or contact page, which reads better to someone who has no account yet.
- The file they asked for is passed to that page as
mf_from, so the page can name what was behind the door. - If the chosen page is unpublished or trashed later, denied visitors fall back to the status code instead of being sent into a 404.
- The access log counts this separately from the login redirect.
1.21.0
- The settings can now be downloaded as a JSON file and read back in on another site, so a configuration worked out on staging does not have to be repeated by hand.
- Only the settings are exported. Share links, the signing key, the access log, and per-file exceptions belong to one site and are never included.
- An imported file is treated as an unsaved change and its effect on the receiving library is reported first, so a scope from another site cannot quietly expose files.
- Every imported value goes through the same sanitizer as the settings form, so a hand-edited file cannot introduce a setting the screen would reject.
- Also available as
wp medifence exportandwp medifence import <file>, with--dry-run.
1.20.0
- A settings change can now be previewed before it is saved: “Preview the effect” applies the values in the form to your library without storing them, and lists the files that would change side.
- Files that would stop being protected are reported first — until now the only way to notice was to save, audit, and find out afterwards.
- The edits stay in the form after a preview, so they can be saved or discarded once the effect is clear.
1.19.1
- The Help tab now covers the access log and its CSV download, which had no entry there at all.
- The developer reference lists the
pausedvalue of the X-Medifence header and thepauseandresumecommands, both introduced in 1.18.0. - Documentation only. Nothing changes in how files are protected.
1.19.0
- The access log can now be downloaded as CSV from the Access log tab — one row per file, with one column per event and machine-readable UTC times.
- Handy for a spreadsheet, or for keeping a record before clearing the log. Until now the CSV was only available on the command line.
1.18.0
- The protection can now be paused for 5, 15, or 60 minutes: every file is served as if the plugin were inactive, and the protection always resumes by itself.
- Deactivating the plugin used to be the only way to rule it out while diagnosing a broken page; a pause does the same without touching the rules — and cannot be forgotten.
- While paused, a warning shows on every admin screen and in Site Health, served files carry an
X-Medifence: pausedheader, and the email notification does not report the pause as a failure. - Also available as
wp medifence pause --minutes=15andwp medifence resume.
1.17.0
- The protection scope can now be filled in by picking an existing folder from the uploads directory, instead of typing the path.
- Configured paths that do not exist are pointed out — a typo used to look exactly like a working setting.
- The capability field suggests the common ones and reports which roles actually have the capability you entered, or warns when no role has it.
1.16.0
- The daily check can now email you when the protection stops working, and again when it works again.
- The message is sent only when the verdict changes, so a site that stays broken does not send one every day.
- A live test that could not reach your site at all is never reported as a failure.
- The address can be set separately from the site administrator’s, and a test email can be sent from the settings screen.
1.15.0
- A single file can now be an exception to the protection scope: always public, or always protected.
- Set it for several files at once with the new bulk actions in the media library, or on a file’s own edit screen.
- Generated image sizes follow their original, and the exceptions are listed on the Audit tab where each can be reset.
- The file inspector reports when “always protected” cannot take effect because an excluded extension or folder keeps the file away from Medifence.
- Also available as
wp medifence override <id> --set=public|protected|default.
1.14.0
- Added an optional access log: it counts, per file, what Medifence did — blocked a request, served the placeholder, sent someone to the login screen, or accepted a share link.
- Off by default. Nothing identifying is stored: no IP addresses, no user agents, no user names — only the file, what happened, and when.
- The 200 most recently seen files are kept, for 30 days, and the log can be cleared at any time.
- Also available as
wp medifence log, with--clear.
1.13.0
- Added an Audit tab: one click applies the current settings to the whole media library and lists every file a logged-out visitor can open, with the reason for each.
- The result also counts the files that are protected and the attachments whose file is missing on disk.
- Also available as
wp medifence audit, which covers the entire library and can write the list to CSV.
1.12.0
- A share link can now be limited to a number of downloads. Once it has been used that often it stops working, even if it has not expired yet.
- Each link can carry a short note, so it is clear later who it was issued to.
- The list of active links shows how often each one was downloaded and when it was last used.
- Reloading a cached file and seeking inside a video are not counted as downloads.
wp medifence shareaccepts--usesand--label.
1.11.0
- Files are now picked from the media library instead of typing an attachment ID by hand — both for the placeholder image and when creating a share link.
- The chosen file’s name is shown next to the field, so it is clear what a stored ID refers to.
1.10.0
- Added an Access column to the media library, showing whether each file is protected or public. Hovering a badge explains which setting decided it.
1.9.0
- Added a file inspector: paste a URL, an attachment ID, or a path, and see whether a logged-out visitor can reach that file — and which setting decided it.
- The verdict names the exact rule responsible (the excluded extension, the excluded path, the parent post, and so on), so a misconfigured scope is easy to spot.
- Also available as
wp medifence inspect <file>.
1.8.0
- Reorganised the settings screen into tabs — Protection status, Settings, Share links, Rules, and a new Help tab — so each part is easier to find.
- Added a status bar that states at a glance whether the protection is active.
- Introduced a stylesheet matching the look of our other plugins. It is loaded only on this screen.
1.7.0
- The settings screen now lists every active share link, with the file it points at and when it expires.
- Any single link can be revoked without affecting the others; expired entries drop off the list on their own.
- Links issued before this version stop working. A link is now valid only while its record exists, which is what makes individual revocation possible.
1.6.0
- Denied requests can now send the visitor to the login screen and back to the file afterwards, instead of returning a status code.
- The redirect only happens for top-level navigation, so images and videos embedded in a page are never replaced by a login page.
1.5.0
- Added a third protection scope: each file follows the post it is attached to. Files on published posts are served to everyone, files on private, draft, or password-protected posts require a login, and files attached to no post stay protected.
- Generated image sizes follow their original attachment, so a thumbnail is never more public than the full image.
1.4.0
- Moved the settings screen from Settings to Media Access Control, next to the library it protects. The screen itself is unchanged; bookmarks pointing at the old location need updating.
1.3.1
- Added a Japanese translation covering the whole admin interface, the Site Health report, and the plugin description.
1.3.0
- Added a daily scheduled check that rewrites the rules when they are missing or outdated (can be turned off).
- Added a Site Health test reporting whether uploaded media files are actually protected.
- Added WP-CLI commands:
wp medifence status,rewrite,remove,verify, andshare.
1.2.0
- Added expiring share links: grant access to one protected file without an account, for 1 hour up to 30 days.
- A share link is the file’s normal URL plus a signature, so it cannot be reused for any other file or kept alive past its expiry.
- Added a “Share link” action to the media library rows, and a “Revoke all share links” button that replaces the signing key.
1.1.0
- Added a protection scope setting: protect everything except the folders you exclude, or protect only the folders you list.
- Excluded folders are served directly by the web server, without loading PHP.
- The same scope is now also evaluated in PHP, so behaviour stays consistent when the .htaccess rules are outdated.
1.0.0
- Initial release.
