DownloadSentinel

Description

DownloadSentinel lets you upload files to WordPress and share special download links that hide the real storage location. Claimed transfers update file counters, while configurable activity logging helps you review download traffic in a clear admin screen.

Under Settings DownloadSentinel you get four tabs:

  • Files — upload or import from Media Library, copy secure links, edit locks, replace files, regenerate tokens
  • Activity — totals, 14-day chart, filters, CSV export, and recent download events
  • Settings — pretty permalinks, force download, bot filtering, tracking, proxy IP trust, and uninstall cleanup
  • About — plugin info and version

How protection works:

  • Files are stored under opaque names in a protected uploads folder blocked from direct browser access
  • Each file gets a unique token URL (/download/{token}/ by default, or ?ds_dl=token). The pretty path base is configurable under Settings.
  • Optional expiry, max downloads, and password per file
  • The plugin streams the file through WordPress, or optionally offloads it to a configured web server—visitors never see the real path
  • Optional shortcode: [downloadsentinel token=”abc…”] (prefer token= over enumerable id=)
  • Dashboard widget for a quick pulse on downloads

Installation

  1. Upload the downloadsentinel folder to the /wp-content/plugins/ directory.
  2. Activate the plugin through the Plugins menu in WordPress.
  3. Open Settings DownloadSentinel.
  4. Upload a file on the Files tab and copy the secure link onto your site.

FAQ

Can visitors guess the real file URL?

Files are stored under opaque random names in a protected uploads folder. Apache .htaccess and IIS web.config deny direct browser access (rewritten on each admin visit if missing). Downloads are only served when a valid token is requested through the plugin. Settings and Tools Site Health run a live canary probe to verify that the folder rejects direct requests. New uploads, imports, and replacements are blocked when the folder is exposed or when protection cannot be verified.

What about Media Library imports?

Import copies the file into protected storage and creates a token link. Delete Media Library original after import is checked by default and required when the attachment URL is still publicly reachable — otherwise the Media URL stays open and token locks (password, expiry, max downloads) do not apply to it.

After deletion, DownloadSentinel verifies that the attachment record, original file, generated image sizes, and known backup images are gone. It retries remaining filesystem cleanup and rolls back the protected import when deletion cannot be verified.

Can I allow ZIP or other risky types with a filter?

No. New uploads use a strict allowlist (documents, images, audio, video). Archives, executables, scripts, SVG/HTML/XML, and similar types stay on a hard disallow list. The downloadsentinel_allowed_mime_types filter cannot reintroduce them — those extensions are stripped from the allow map. Older protected files that still use a blocked extension are refused at download time and flagged on the Files screen; delete or replace them with an allowed type.

What about nginx?

.htaccess is ignored on nginx. Add a deny rule for the protected folder (opaque filenames alone are not enough if the directory is publicly readable):

location ^~ /wp-content/uploads/downloadsentinel/ {
deny all;
}

Then reload nginx. Without this rule, nginx may serve files directly and bypass password, expiry, and download limits.

When nginx is detected, or when a live probe finds the folder exposed or cannot verify protection, DownloadSentinel shows guidance on its settings screen and under Tools Site Health. Writes remain blocked until the probe confirms that direct access is denied.

How should I embed a download link?

Prefer the token shortcode shown on each file card: [downloadsentinel token=”…”]. Do not use id= in public content — numeric IDs are enumerable. Resolving by id= requires an administrator capability by default and never renders files that have no password.

Behind a reverse proxy?

By default the plugin uses REMOTE_ADDR for password lockouts and activity hashing. If your host sits behind Cloudflare or another proxy that overwrites client IP headers, enable Trust proxy IP headers in Settings. Do not enable it on a publicly reachable origin that does not sanitize X-Forwarded-For.

How are password attempts limited?

Password submissions are limited to 10 attempts per download token and client IP, followed by a 15-minute lockout. Attempt reservations are serialized before password verification so concurrent requests cannot exceed the limit. If synchronization is temporarily unavailable, verification fails closed for a few seconds instead of allowing an uncounted attempt.

After a correct password, DownloadSentinel issues a signed, HTTP-only authorization cookie for that token and password. The cookie lasts for one hour and is automatically invalidated when the file password changes.

What download activity data is stored?

Depending on the tracking settings, activity records can contain the download time, file ID, a salted hash of the client IP address, WordPress user ID, user agent, and referrer URL. Raw IP addresses are not stored. Bot activity and logged-in-user activity can be excluded from the activity log; file counters still count every claimed transfer.

Activity logs can be exported or cleared from the Activity tab. By default, uninstall preserves protected files and database tables, including activity records. Enable Delete files on uninstall before uninstalling if you want the plugin to purge its stored files and tables.

Will pretty links work without permalinks?

If WordPress permalinks are plain, or pretty permalinks are disabled in Settings, links fall back to a query-string format that still works.

The pretty path base defaults to /download/{token}/. Change Pretty link base under Settings DownloadSentinel if that slug conflicts with a page or another plugin.

After deactivating or uninstalling, rewrite rules are flushed automatically. If an old pretty URL still resolves oddly, open Settings Permalinks and click Save Changes once.

Does uninstall delete my files?

Only if you enable Delete files on uninstall in Settings. By default, uninstall removes plugin settings only — protected files and database tables stay on the server until you turn that option on or delete them manually.

What happens if deleting a protected file fails?

Deletion first disables token access, then removes the stored bytes and asks the database layer to delete the record and associated activity logs. Success is shown only after the file and record removals report success. If filesystem or database cleanup fails, the retained record stays disabled so an administrator can correct the underlying problem and safely retry deletion.

Does it work on WordPress Multisite?

Yes. Network-activating sets up every site (tables, protected storage, rewrites). New sites created while the plugin is network-active are set up automatically. Uninstall and network deactivation also walk all sites.

How do max download limits work?

Each successful GET that starts streaming the file claims one slot before the body is sent, so concurrent requests cannot oversell the limit. Expiry is also enforced in that atomic claim. If the server cannot open or read the file, that slot is released. Client disconnects after transfer begins still count — otherwise visitors could abort and retry to bypass the cap. Long transfers call set_time_limit(0) when allowed so PHP max_execution_time is less likely to cut off mid-stream after a claim.

DownloadSentinel supports single HTTP byte ranges, so browsers and download clients can resume interrupted transfers. Each range request is a separate claimed GET and therefore counts toward a file’s max-download limit.

Can large downloads be served without tying up PHP workers?

Yes, when the web server is configured for internal file offloading. Developers can use the downloadsentinel_offload_response filter to return an array with header and value keys. The only accepted headers are X-Sendfile and X-Accel-Redirect.

For Apache mod_xsendfile, the value is normally the authorized absolute path:

add_filter(
    'downloadsentinel_offload_response',
    function ($offload, $file, $path) {
        return array(
            'header' => 'X-Sendfile',
            'value'  => $path,
        );
    },
    10,
    3
);

For nginx, the value must be an internal URI backed by a correctly configured internal location and alias:

add_filter(
    'downloadsentinel_offload_response',
    function ($offload, $file, $path) {
        return array(
            'header' => 'X-Accel-Redirect',
            'value'  => '/_downloadsentinel/' . rawurlencode(basename($path)),
        );
    },
    10,
    3
);

Do not enable either response until the corresponding server module or internal location is configured and tested. An unhandled X-Sendfile header can disclose the filesystem path without serving the file.

Without server offloading, the plugin uses bounded 1 MiB chunks (filterable with downloadsentinel_stream_chunk_size) and sends only the requested range. Chunking limits PHP memory use, but the PHP worker remains occupied for the duration of the transfer.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“DownloadSentinel” is open source software. The following people have contributed to this plugin.

Contributors

Translate “DownloadSentinel” into your language.

Interested in development?

Browse the code, check out the SVN repository, or subscribe to the development log by RSS.

Changelog

1.0.0

  • Initial release.