Title: Medifence – Access Control for Media Uploads
Author: Redwoodcity
Published: <strong>July 25, 2026</strong>
Last modified: July 29, 2026

---

Search plugins

![](https://ps.w.org/medifence/assets/banner-772x250.png?rev=3622612)

![](https://ps.w.org/medifence/assets/icon-256x256.png?rev=3622612)

# Medifence – Access Control for Media Uploads

 By [Redwoodcity](https://profiles.wordpress.org/redwoodcity/)

[Download](https://downloads.wordpress.org/plugin/medifence.1.63.1.zip)

 * [Details](https://pcd.wordpress.org/plugins/medifence/#description)
 * [Reviews](https://pcd.wordpress.org/plugins/medifence/#reviews)
 *  [Installation](https://pcd.wordpress.org/plugins/medifence/#installation)
 * [Development](https://pcd.wordpress.org/plugins/medifence/#developers)

 [Support](https://wordpress.org/support/plugin/medifence/)

## 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
 * Either way they are told which file they were opening, so the redirect does not
   read as a broken site
 * Optionally serve a placeholder image instead of denied images — your own, or 
   the grey padlock included with the plugin
 * 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
 * Narrow the media library list to one protection state, to select what is exposed
   and fix it without leaving the screen
 * 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
 * Or a whole set at once, as one message listing every link with its file name 
   and expiry
 * Start a link from a file’s own edit screen, with the file already selected
 * Download the ledger of active links as CSV — without the links themselves, so
   a leaked ledger leaks no access
 * Search that list by file name or note, or show only what expires within a day,
   and page through it
 * 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
 * Download that list as CSV, from the admin screen — the evidence a compliance 
   process asks for
 * 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
 * Or be told at once: publishing a post checks that post and names the files its
   visitors will not be able to open
 * 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
 * Including the share links that were refused, and the wrong passwords: why “the
   link you sent does not open”
 * 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
 * Choose whether allowed files open in the browser or always arrive as a download—
   video and audio keep playing either way
 * 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 `.htaccess` file 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

[⌊Protection status: the live test without a session cookie, the scheduled checks,
and the verdict for a single file⌉⌊Protection status: the live test without a session
cookie, the scheduled checks, and the verdict for a single file⌉[

Protection status: the live test without a session cookie, the scheduled checks,
and the verdict for a single file

[⌊The protection scope: excluded and protected folders, the image sizes anyone may
see, and per-folder capabilities⌉⌊The protection scope: excluded and protected folders,
the image sizes anyone may see, and per-folder capabilities⌉[

The protection scope: excluded and protected folders, the image sizes anyone may
see, and per-folder capabilities

[⌊What a denied visitor receives, how an allowed file is delivered, the notifications,
and the record of which administrator changed the protection⌉⌊What a denied visitor
receives, how an allowed file is delivered, the notifications, and the record of
which administrator changed the protection⌉[

What a denied visitor receives, how an allowed file is delivered, the notifications,
and the record of which administrator changed the protection

[⌊The audit: which files anyone can open and why, files no attachment points at,
what became reachable since the last count — and the list as CSV⌉⌊The audit: which
files anyone can open and why, files no attachment points at, what became reachable
since the last count — and the list as CSV⌉[

The audit: which files anyone can open and why, files no attachment points at, what
became reachable since the last count — and the list as CSV

[⌊Share links: expiring signed links with a password, a download limit, a note, 
a list you can narrow, and a ledger to download⌉⌊Share links: expiring signed links
with a password, a download limit, a note, a list you can narrow, and a ledger to
download⌉[

Share links: expiring signed links with a password, a download limit, a note, a 
list you can narrow, and a ledger to download

[⌊The .htaccess rules the plugin generates⌉⌊The .htaccess rules the plugin generates⌉[

The .htaccess rules the plugin generates

## Installation

 1. Upload the `medifence` folder to `/wp-content/plugins/`, or install it from the
    Plugins screen.
 2. Activate the plugin. The `.htaccess` rules are written automatically.
 3. Go to **Media  Access Control** and confirm that the live test reports “Anonymous
    access is blocked”.
 4. 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 `.htaccess` rewrite 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 `.htaccess` file 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_limit` filter changes that.

### Does this modify files outside the plugin directory?

Yes, one file: the `.htaccess` inside your uploads directory. The plugin uses WordPress’s`
insert_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, accepted a share link — or refused one, because it 
had expired, been revoked, or been used up, with wrong passwords counted separately.
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: 
private` on 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.

### I serve my media from S3 or a CDN. Does this still protect them?

No — and the plugin now says so rather than leaving you to find out. When your media
URLs point at another host, requests for those files never reach this server: the
uploads `.htaccess` is not on the way, and Medifence is never asked who may open
them. Everything else keeps looking healthy, which is what makes this worth naming—
the rules are in place, and the live test requests a file from this site and passes.

The status tab and Site Health name the host whenever the media URLs point elsewhere.
Protect the files where they are actually served from, or serve the uploads directory
from this site again. A CDN that caches your site as a whole is fine, as long as
the media URLs still point at your domain; if your setup rewrites the URLs but the
files do pass through WordPress after all, correct the verdict with the `medifence_external_media`
filter.

### 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/gold` is applied ahead of
one on `members`. 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 `-scaled` copy 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 `-1024x1024` match. 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.

The login screen also names the file they were opening, above the form, so the redirect
reads as a closed door rather than as a site that lost their click. Only the name
is shown, never the folder it sits in.

### My gallery shows broken images to logged-out visitors. Can I show something instead?

Yes. Under “Placeholder image” on the settings tab, either choose an image of your
own from the media library, or tick “Use the built-in image when no attachment is
chosen” — a grey padlock that ships with the plugin, with no text on it, so it reads
the same in every language.

Denied image requests then receive that picture instead of a status code, and the
layout of the page survives. The original file is never served. This applies to 
images only; other file types still get the status code. If a placeholder attachment
you chose is deleted later, the built-in image stands in for it, provided the tick
box is on.

### 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 `.htaccess` rules 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 an `X-
Medifence: paused` header, a warning shows on every admin screen, and the email 
notification does not report the pause as a failure. Also available as `wp medifence
pause` and `wp 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.json` and`
wp medifence import medifence.json` do the same, and `--dry-run` reports 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`, and the `[
medifence_requested_file]` shortcode prints its name, so the page can say “You tried
to open [medifence_requested_file]” without any PHP. Opened directly, with no file
behind it, the shortcode prints nothing — so write the sentence to still read sensibly
then.

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.

### My published post shows broken images to visitors. Why, and how do I catch it earlier?

Because the post links to files the protection keeps closed: you are logged in and
see them, a visitor is not and does not. It is the one misconfiguration this plugin
can create, and it looks perfectly fine to whoever wrote the post.

Publishing a post now checks that post for such files and names them in a notice,
with a link back to the post — no scan to remember, and nothing is blocked: the 
post is saved either way. Since the block editor saves in the background, the notice
appears on the next admin screen rather than the instant you press publish. A post
whose files are all reachable says nothing at all.

To review the whole site instead, use “Scan published content” on the Audit tab.
The fix is either to widen the protection for those files (an exception, an excluded
folder, or the “follow the parent post” scope) or to use a public copy in the post—
the audit names each file so the choice is per file.

### 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. The
list can be searched by file name or note and filtered to what expires within a 
day, what has one download left, or what carries a password, with pages of 50. “
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.

The whole set can be emailed from that result box, as one message listing every 
link with its file name and expiry — the recipient is one person, so twenty separate
mails would be worse than the paste this replaces. If any of the links has been 
revoked between issuing and sending, nothing is sent at all.

## 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.

Contributors

 *   [ Redwoodcity ](https://profiles.wordpress.org/redwoodcity/)

[Translate “Medifence – Access Control for Media Uploads” into your language.](https://translate.wordpress.org/projects/wp-plugins/medifence)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/medifence/), check 
out the [SVN repository](https://plugins.svn.wordpress.org/medifence/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/medifence/) by [RSS](https://plugins.trac.wordpress.org/log/medifence/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.63.1

 * Screenshots retaken on the current screens. The previous set was from 1.53.1 
   and did not show the delivery setting, the audit’s CSV download, or the filter
   over the share links.
 * Their captions were updated to match. No change to the plugin itself.

#### 1.63.0

 * New setting: whether an allowed file opens in the browser or always arrives as
   a download. Until now the file type decided alone, and a site that wants its 
   PDFs saved rather than read in a tab had no say.
 * Share links follow the same setting, so a file behaves the same however the visitor
   was allowed in.
 * Video and audio keep opening in place under either choice: a download is not 
   something a player on your page can play, so including them would break embedded
   media rather than change a preference.
 * Files with an excluded extension or in an excluded folder are handed out by the
   web server and never reach the plugin, so the setting has no say over them. The
   screen says so.

#### 1.62.0

 * The media library list can now be narrowed to the files that are protected, or
   to the files anyone can open, with a dropdown next to the date filter — the short
   way to select what is exposed and fix it with one bulk action.
 * The verdict is worked out per file and stored nowhere, so it cannot be part of
   the database query: the dropdown narrows the page you are looking at, not the
   library. The screen says so, because an almost empty page is not an answer about
   the library.
 * Page numbers and the total keep counting every file. To go through the whole 
   library, the Audit tab is still the tool.

#### 1.61.0

 * The list of active share links can now be narrowed: search a file name or a note,
   or show only the links expiring within a day, down to their last download, or
   carrying a password.
 * Lists longer than 50 links are split into pages. A site that issues links continuously
   had no way to find one except by scrolling.
 * The filter reads the records already loaded — no new query, and nothing is stored.
   The CSV export still covers every active link whatever the list is filtered to:
   a ledger of a filtered view would not be a ledger.

#### 1.60.0

 * The plugin now ships a placeholder image of its own — a grey padlock — for sites
   that just want denied images to look deliberate without preparing one first.
 * Tick “Use the built-in image when no attachment is chosen” under the placeholder
   setting. Off by default, so nothing changes for anyone who has not asked for 
   it.
 * It also stands in when a chosen placeholder attachment has been deleted since.
   Until now that quietly turned the placeholder off and denied images went back
   to being errors.
 * The image carries no text, so it reads the same in every language, and it is 
   a plain file in the plugin — not generated at runtime.

#### 1.59.0

 * The audit’s list of files anyone can open can now be downloaded as CSV from the
   Audit tab: the file, the attachment, the deciding setting, and the explanation
   in words.
 * The access log and the share link ledger have offered this for versions; the 
   audit — the list a compliance process actually asks for — was available on the
   command line only.
 * The file holds exactly what is on screen and never runs a fresh scan, so it cannot
   disagree with the list it came from. While a large library is still being worked
   through in batches, the screen says so next to the button rather than letting
   the file imply completeness.

#### 1.58.0

 * The access log now counts share links that were presented and refused — expired,
   revoked, or used up — and wrong passwords, each on its own.
 * “I sent them a link and they say it does not open” has exactly those two answers,
   and until now the log could not tell them apart: a refused link disappeared into
   the general blocked count, and a wrong password was recorded nowhere at all.
 * The new counts are added as two new columns at the end of the CSV, so anything
   built on an earlier export keeps working. A refused link is counted twice on 
   purpose: once as the link that failed, once as the response the visitor received.
 * Still nothing identifying is stored — no IP addresses, no user agents, no names.

#### 1.57.0

 * A set of share links issued together can now be emailed from the result box as
   well — one message listing every link with its file name and expiry, not one 
   message per file.
 * If any link in the set has been revoked in the meantime, nothing is sent. A message
   that quietly drops half of what it promises is worse than an error.
 * A share link can now also be started from a file’s own edit screen, under “Medifence
   share link”: it opens the share form with that file already selected.

#### 1.56.0

 * A visitor sent to the login screen is now told which file they were opening, 
   above the form. A login form appearing out of nowhere reads as a broken site 
   rather than as a closed door.
 * The name is read from the redirect the browser already carries, so nothing is
   added to the URL, and whatever else asks to be on that screen stays where it 
   was.
 * For the “page of your own” option, the new `[medifence_requested_file]` shortcode
   prints the same name — so the page can say “You tried to open report.pdf” without
   writing any PHP.
 * Only the file name is ever shown, never the folder it sits in, and only for a
   file that exists in this site’s uploads directory: the address it comes from 
   is one anyone can type.

#### 1.55.0

 * Publishing a post now checks that post for media its visitors cannot open, and
   names the files in a notice with a link back to the post.
 * The published-content scan on the Audit tab answers the same question, but only
   for whoever thinks to run it — while a broken image is visible from the moment
   the post goes live. This is the same verdict, delivered to the person who wrote
   the post.
 * Nothing is blocked and nothing is changed: the post is saved either way, and 
   a post whose files are all reachable says nothing at all. The block editor saves
   in the background, so the notice appears on the next admin screen.
 * Up to 30 media links per post are examined; a post with more says so rather than
   implying it was fully checked.

#### 1.54.0

 * Media served from another host — an offload plugin, or a CDN pointed at the uploads
   directory — is now named on the status tab and in Site Health as what it is: 
   files this plugin is never asked about.
 * This was the one failure that looked like success. The rules are in place, the
   live test requests a file from this site and passes, the audit calls the library
   protected — while the files themselves are handed out by somewhere else entirely.
 * Judged from the media URLs alone: no HTTP request, no scan, and no list of plugin
   names to go out of date. The new `medifence_external_media` filter corrects the
   verdict for a setup where the files do pass through this site after all.

#### 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=json` and `wp medifence inspect --format=json` 
   print 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.
 * `audit` and `stray` already 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 `medifence` field 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 `-scaled` copy 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 audit` now covers the whole library by default; pass `--limit` 
   to 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/gold` is applied ahead of
   one on `members`.
 * 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_limit` filter 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 export` and `wp 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 `paused` value of the X-Medifence header and
   the `pause` and `resume` commands, 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: paused` header, and the email notification does 
   not report the pause as a failure.
 * Also available as `wp medifence pause --minutes=15` and `wp 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, …

## Meta

 *  Version **1.63.1**
 *  Last updated **2 weeks ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.0 or higher **
 *  Tested up to **7.0.4**
 *  PHP version ** 7.4 or higher **
 *  Language
 * [English (US)](https://wordpress.org/plugins/medifence/)
 * Tags
 * [access-control](https://pcd.wordpress.org/plugins/tags/access-control/)[media](https://pcd.wordpress.org/plugins/tags/media/)
   [membership](https://pcd.wordpress.org/plugins/tags/membership/)[protection](https://pcd.wordpress.org/plugins/tags/protection/)
   [uploads](https://pcd.wordpress.org/plugins/tags/uploads/)
 *  [Advanced View](https://pcd.wordpress.org/plugins/medifence/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/medifence/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/medifence/reviews/)

## Contributors

 *   [ Redwoodcity ](https://profiles.wordpress.org/redwoodcity/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/medifence/)