DEV Community

WPlend.net
WPlend.net

Posted on

How to check a WordPress plugin ZIP before installing

A plugin ZIP reaches you in many ways: a client sends it by email, you find it in an old backup, you download it from a marketplace or a GPL redistributor, a freelancer leaves it in a shared folder. Once you click Upload Plugin, every PHP file in that archive runs with full access to the site and its database.

Here is the routine I use before installing any archive that did not come straight from wordpress.org. It takes about ten minutes and only needs a terminal.

All examples use Linux/macOS. On Windows, run them in WSL.

1. Compare the checksum

If the source publishes a SHA-256 checksum, check it first. It tells you the file you have is exactly the file they published — not truncated, not re-packed, not swapped on the way.

sha256sum plugin.zip            # Linux
shasum -a 256 plugin.zip        # macOS
Enter fullscreen mode Exit fullscreen mode
Get-FileHash -Algorithm SHA256 plugin.zip   # Windows PowerShell
Enter fullscreen mode Exit fullscreen mode

The output must match the published value character for character. If it does not, stop here.

A matching checksum proves file identity, not safety. It only means you have the same file the source has.

2. Look inside without installing

List the contents before unpacking:

unzip -l plugin.zip | less
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • One top-level folder named like the plugin (my-plugin/). Several unrelated folders, or files at the root, are worth a second look.
  • Paths with ../ or starting with /. A normal plugin never has them.
  • Unexpected file types: .exe, .sh, .phar, .so, or PHP files with double extensions like image.png.php.

Now unpack it into a throwaway folder:

mkdir -p /tmp/zipcheck && unzip -q plugin.zip -d /tmp/zipcheck
cd /tmp/zipcheck
Enter fullscreen mode Exit fullscreen mode

A few quick searches:

# PHP files where there should only be assets
find . -iname "*.php" \( -path "*images*" -o -path "*img*" -o -path "*fonts*" -o -path "*css*" \)

# hidden files and .htaccess
find . -name ".*" -type f

# patterns common in injected code
grep -rnE "eval\s*\(|gzinflate\s*\(|str_rot13\s*\(|shell_exec\s*\(|base64_decode\s*\(" --include=*.php . | head -50
Enter fullscreen mode Exit fullscreen mode

The last search will have false positives — plenty of legitimate plugins use base64_decode for icons or API payloads. What you are looking for is the classic shape of injected code: a long unreadable string fed into eval(base64_decode(...)) or eval(gzinflate(...)), often on the first line of a file or appended after the closing ?>.

3. Read the headers

Every plugin has a main PHP file with a header comment; every theme has one in style.css. Plugins usually repeat the requirements in readme.txt.

grep -rhE "^[ \t/*#@]*(Plugin Name|Theme Name|Version|Requires at least|Tested up to|Requires PHP|WC tested up to):" \
  --include=*.php --include=style.css --include=readme.txt . | sort -u
Enter fullscreen mode Exit fullscreen mode

Check three things:

  • Version is the one you expected. Mislabeled archives are common — you think you are installing 3.2 and it is actually 2.9.
  • Requires PHP is not higher than what your server runs.
  • Tested up to is not years behind your WordPress version.

These values are declared by the developer. They are not a test result, but a mismatch is a good reason to ask questions before installing.

4. Run a PHP syntax check

php -l parses a file without running it. It catches truncated downloads, broken re-packs and files that were edited by hand and saved with an error — the kind of thing that gives you a white screen right after activation.

find . -type f -name "*.php" -print0 | xargs -0 -n1 php -l | grep -v "^No syntax errors"
Enter fullscreen mode Exit fullscreen mode

No output means every file parsed. Run it with the same PHP major version as your server, since newer syntax fails on older PHP.

5. Scan with ClamAV

ClamAV is a free, open-source antivirus engine with signatures for common PHP web shells and injected code.

# Debian / Ubuntu
sudo apt install clamav
sudo systemctl stop clamav-freshclam   # if the service is running
sudo freshclam                         # update signatures
sudo systemctl start clamav-freshclam

# macOS
brew install clamav   # then follow brew's notes to create freshclam.conf
freshclam
Enter fullscreen mode Exit fullscreen mode
clamscan -r -i /tmp/zipcheck
Enter fullscreen mode Exit fullscreen mode

-i prints only infected files. Look at the summary at the end: Infected files: 0 is what you want.

A clean result means no known signature matched. It is not a code audit, and it will not catch a brand-new or hand-written backdoor — that is why steps 2 and 4 are still worth doing.

6. Read the changelog

Find the entry for this version in readme.txt (== Changelog == section), changelog.txt or CHANGELOG.md. It tells you whether you are about to apply a security fix, a major rewrite, or a release that changes database tables — which decides whether you need a backup and a maintenance window.

7. Install on staging first

Everything above is static. None of it shows how the plugin behaves with your theme, your other plugins and your PHP settings. Install on a staging copy, enable WP_DEBUG_LOG, click through the pages that matter and read wp-content/debug.log before you touch production.

Clean up when you are done:

rm -rf /tmp/zipcheck
Enter fullscreen mode Exit fullscreen mode

Doing it in one command

Steps 1–6 are the same every time, so I put them into a single PHP script: wp-zip-check. It calculates checksums, unpacks safely (skipping ../ and absolute paths), counts files by type, reads the headers, compares the version, runs php -l on every file, scans with ClamAV and prints the changelog entry for that version.

curl -O https://raw.githubusercontent.com/wplend/wp-zip-check/main/wp-zip-check.php
php wp-zip-check.php plugin.zip --expect=1.2.0
Enter fullscreen mode Exit fullscreen mode

It exits with 0 when all checks pass and 1 when one fails, so you can run it over a folder of archives or in CI. --json gives a machine-readable report. It is GPL-licensed, one file, no dependencies besides php-zip and ClamAV.

Quick checklist

  • [ ] SHA-256 matches the published value
  • [ ] One top-level folder, no ../, no unexpected file types
  • [ ] No PHP in asset folders, no eval(base64_decode(...)) blobs
  • [ ] Version, Requires PHP and Tested up to make sense
  • [ ] php -l passes on every file
  • [ ] ClamAV: 0 infected files with fresh signatures
  • [ ] Changelog read, backup taken
  • [ ] Tested on staging

Disclosure: I work on WPlend, where every plugin and theme archive goes through these same automated checks after upload. wp-zip-check is the standalone version of that scanner.

Top comments (0)