WP Manifestindependent plugin directory
manifest / media / upload-sleuth

UploadSleuth – Media Audit & Cleanup releases

Safety-first WordPress plugin for auditing uploads, detecting likely stray media and broken Media Library records, with quarantine, verified backups, guarded cleanup, AJAX workflows, and WP-CLI support.

by Vagelis P. · github.com/vagelisp/upload-sleuth

0stars
6release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/vagelisp/upload-sleuth/releases/download/v1.0.2/upload-sleuth-1.0.2.zip

Readme

UploadSleuth – Media Audit & Cleanup

UploadSleuth is a cautious WordPress uploads auditor. It inventories files on disk, removes known Media Library files and their generated variants from consideration, searches WordPress database content for textual references, and presents the remainder as likely stray candidates.

It is designed for administrators and developers who need evidence before cleaning an old or bloated wp-content/uploads directory.

A missing reference is not proof that a file is unused. Back up the site, review every result, run a dry run, and prefer quarantine over permanent deletion.

Highlights

  • AJAX admin workflow under Tools → UploadSleuth—no page reload during scans or file actions.
  • Batched dashboard scans with live progress, cancellation, and saved partial findings.
  • Separate Media Library integrity checks for missing local originals and generated sizes.
  • Result summary with scanned files, attachment matches, database matches, candidate count, and potential disk space.
  • Persistent cleanup statistics for reclaimed space, permanent removals, quarantine moves, and restores.
  • Search, sort, bulk selection, dry-run, quarantine, verified backup-and-remove, and guarded permanent deletion.
  • Per-user results retained for 12 hours.
  • Attachment-aware matching for originals, generated sizes, edited originals, and backup sizes.
  • Reference checks across posts, excerpts, GUIDs, post meta, options, term meta, user meta, and comment meta.
  • Optional custom-table detection and broad non-core table scanning.
  • WP-CLI output in table, CSV, JSON, or YAML format.
  • Complete in-dashboard CLI reference with copy-ready recipes.
  • CLI filters for candidate age and size, CI exit behavior, and explicit deletion confirmation.
  • Local-only operation. The plugin does not call an external service.

Requirements

  • WordPress 6.0 or newer
  • PHP 7.4 or newer
  • WP-CLI for command-line usage
  • An administrator account for the dashboard

Installation

  1. Copy the plugin directory to wp-content/plugins/upload-sleuth.
  2. Activate UploadSleuth in WordPress.
  3. Open Tools → UploadSleuth.
  4. Save any ignore or custom-table settings before the first broad scan.

Recommended workflow

  1. Make a verified filesystem and database backup.
  2. Scan a narrow uploads subdirectory such as 2026/08.
  3. Keep database checking enabled for the most conservative result.
  4. Filter and inspect the candidates.
  5. Select a small batch and run a dry-run quarantine.
  6. Disable dry-run and quarantine the files.
  7. Test the public site and administrative workflows.
  8. Restore any file that proves necessary, or remove old quarantine runs manually after an appropriate retention period.

The plugin deliberately labels results as candidates. References can live in encoded values, files, remote services, custom database schemas, or runtime-generated code that a textual database search cannot discover.

Dashboard

Scan options

  • Uploads subdirectory: Restricts inventory to a path relative to the uploads root.
  • Database check limit: Stops database checks after a fixed number of candidates. 0 means unlimited. A limited scan is incomplete and is marked as such.
  • Include non-media files: Reports extensions not registered in the WordPress MIME map. Quarantine, restore, backup, and deletion still reject them.
  • Fast scan: Skips database text searches. This is faster but produces lower-confidence candidates.

Dashboard reference checks use a configurable batch size of 25 by default. Stop scan preserves findings through the latest completed batch and marks them as partial. Partial results never imply that the remaining candidates are clean; they simply have not been classified yet. Saved partial rows can still be reviewed or used with dry-run and quarantine. Last-moment reference validation remains enabled by default and can be disabled in advanced settings when an administrator explicitly accepts the additional risk.

Slow-server tuning

  • Scan batch size: Candidates checked per scan request; reduce it when scan steps time out.
  • File-action batch size: Files deleted or quarantined per request; smaller batches provide more frequent progress updates.
  • Pause between batches: Adds breathing room between action requests on constrained servers.
  • Rows rendered per view: Limits findings and action-report DOM nodes while keeping the complete data available through Load more.
  • Revalidate references: Recommended and enabled by default. Disabling it skips the expensive current-reference lookup but retains capability, nonce, saved-finding, and path-safety checks.

Settings

  • Ignore patterns: One glob per line. Examples: cache/*, tmp/, or *.webp.
  • Custom table checks: Comma-separated table:column pairs. The current WordPress prefix can be omitted.
  • Scan all non-core tables: Detects and searches text-like columns in other tables. This can be expensive.
  • Quarantine directory: Stored below the dedicated uploads/upload-sleuth directory and automatically excluded from future audits.

File actions

  • Dry run: Opt-in preview that reports the planned action without touching files.
  • Quarantine: Moves files into a timestamped directory while preserving relative paths.
  • Restore: Lists quarantined files in the dashboard and moves a selected file back to its original uploads path. An existing destination is never overwritten.
  • Quarantine deletion: Permanently deletes selected recoverable quarantine entries or all recoverable entries; backup ZIPs are excluded.
  • Download ZIP & remove (dashboard): Builds one ZIP, reads every archived entry back to verify its byte count and SHA-256 hash, and only then removes the originals and starts the download.
  • Backup & remove (WP-CLI): Builds one ZIP below the protected UploadSleuth storage directory, verifies every entry by size and SHA-256, and only then removes the originals.
  • Delete: Permanently unlinks selected findings or the entire saved finding set and cannot be undone. The UI requires confirmation when dry-run is disabled.

Every submitted path is checked against the current user's saved findings and normalized as a safe relative uploads path before an action is attempted. Filesystem actions are restricted to file types recognised by WordPress, symbolic links are rejected, and quarantined files receive a non-executable storage suffix.

Media Library integrity

The Library integrity tab audits the other side of the uploads relationship: attachment database records whose expected local original no longer exists. It also reports attachment metadata that names generated image sizes missing from disk while the original remains available.

The check runs in configurable batches, shows real completed/total progress, and can be stopped after the current request. Completed findings remain available. Missing generated sizes are report-only and can usually be addressed with a thumbnail-regeneration tool after the source image is verified.

Only missing-original records can be selected for cleanup. Immediately before deletion, the server confirms that the attachment came from the saved integrity result and that its local original is still absent. It then calls wp_delete_attachment($id, true) so WordPress removes the post, attachment metadata, relationships, known remaining generated files, and runs normal attachment-deletion hooks.

WordPress can delete the database record even if an individual companion file cannot be removed. UploadSleuth compares known local companions before and after the call, flags any survivors in the per-record outcome, and counts only files that actually disappeared as reclaimed storage.

Object-storage and media-offload plugins may intentionally remove local files while retaining valid remote media. The integrity screen cannot prove a remote object is absent. Review those records with the offload provider before deleting anything; both the screen and confirmations call out this risk.

WP-CLI

The command is:

wp upload-sleuth

Useful examples

# Audit all registered media files.
wp upload-sleuth

# Audit one year and return machine-readable results.
wp upload-sleuth --uploads-subdir=2025 --format=json

# Only show candidates at least 100 KB and older than 90 days.
wp upload-sleuth --min-size=100 --older-than=90

# Print counts and space without listing every file.
wp upload-sleuth --summary-only

# Preview quarantine operations.
wp upload-sleuth --quarantine --dry-run

# Move all findings into quarantine.
wp upload-sleuth --quarantine

# Preview a verified backup-and-remove run, then explicitly confirm it.
wp upload-sleuth --backup-delete --dry-run
wp upload-sleuth --backup-delete --yes

# Preview permanent deletion, then explicitly confirm it.
wp upload-sleuth --delete --dry-run
wp upload-sleuth --delete --yes

# Fail a CI or maintenance job when candidates exist.
wp upload-sleuth --summary-only --fail-on-findings

Options

Option Purpose
--uploads-subdir=<path> Restrict the scan below uploads.
--limit=<number> Limit database-checked candidates; 0 is unlimited.
--min-size=<kb> Only report candidates at least this large.
--older-than=<days> Only report candidates older than this age.
--all-files Report non-media extensions; filesystem actions still reject them.
--skip-db-check Use attachment metadata only.
--ignore=<patterns> Add comma-separated glob patterns.
--ignore-file=<path> Read newline-separated patterns, with # comments.
--custom-tables=<list> Add table:column reference checks.
--scan-all-tables Search text columns in non-core tables.
--format=<format> Use table, csv, json, or yaml.
--summary-only Suppress individual finding rows.
--quarantine Move findings to quarantine.
--backup-delete Create verified backups, then remove originals.
--delete Permanently remove findings.
--dry-run Simulate the chosen file action.
--yes Required for a real --delete.
--quarantine-dir=<path> Override the quarantine directory.
--fail-on-findings Exit non-zero when candidates exist.

--quarantine, --backup-delete, and --delete are mutually exclusive. Real backup-and-remove and permanent-delete operations require --yes.

Scan diagnostics

Enable Browser console diagnostics in Settings, then open the browser developer console before starting a dashboard scan to see submitted options, a heartbeat every five seconds, errors, elapsed request time, and a final result summary. File-action samples are capped at 100 rows. Diagnostics are disabled by default, and their heartbeat timer and large payload construction do not run while disabled.

WP-CLI reports when filesystem inventory and attachment indexing complete, then prints a milestone for every 250 database candidates checked. These messages make long scans observable without changing machine-readable finding rows.

What is checked

UploadSleuth builds an attachment reference index from _wp_attached_file and _wp_attachment_metadata. It recognizes the main file, generated image sizes, original_image, and backup_sizes.

For files not in that index, it searches common textual storage locations:

  • posts.post_content, posts.post_excerpt, and posts.guid
  • post, term, user, and comment metadata
  • WordPress options
  • explicitly configured custom table columns
  • optionally detected text, character, and JSON columns in non-core tables

URL-encoded URLs, plain URLs, /uploads/ fragments, and relative paths are considered.

Known limitations

  • Initial filesystem inventory and attachment indexing occur in the first request; exceptionally large libraries can still make that preparation request expensive. Database candidate checks are batched and stoppable.
  • Binary, encrypted, compressed, or unusually encoded references may not match.
  • External databases, object storage, APIs, CSS/JavaScript/PHP files, and remote content are outside the default search.
  • The integrity check tests the local filesystem only, so intentionally offloaded media can appear as missing and must be verified with its remote provider.
  • A database-check limit makes the result incomplete.
  • The restore browser lists up to 500 quarantined files at a time; backup ZIPs are intentionally excluded.
  • Files removed directly from disk do not trigger WordPress attachment lifecycle hooks.

Security model

  • Dashboard access requires manage_options.
  • AJAX requests require a user nonce.
  • Settings and submitted paths are sanitized.
  • File actions only accept paths present in the current user's saved audit.
  • Attachment metadata and database references are rechecked immediately before every action.
  • Files that became referenced after scanning are blocked and left untouched.
  • Traversal-like or control-character paths are rejected.
  • Standalone files are removed through WordPress wp_delete_file() and its filter.
  • Registered attachments are never removed as raw files. Integrity cleanup accepts only saved missing-original records, rechecks the original, and uses wp_delete_attachment().
  • Real CLI deletion requires --yes.

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v1.0.2 Sep 9, 2026 upload-sleuth-1.0.2.zip.sha256 1
v1.0.2 Sep 9, 2026 upload-sleuth-1.0.2.zip 2
v1.0.1 Aug 19, 2026 media-audit-1.0.1.zip 3
v1.0.1 Aug 19, 2026 media-audit-1.0.1.zip.sha256 1
v1.0.0 Aug 19, 2026 media-audit-1.0.0.zip 1
v1.0.0 Aug 19, 2026 media-audit-1.0.0.zip.sha256 1