Repository navigation
How It Works
The files
| File | Job |
|---|---|
jat-shell-scanner.php |
Plugin header, constants, loads everything |
includes/signatures.php |
All indicators — fixed PHP and WordPress versions, log patterns, hashes, IPs, code patterns. Easy to review and extend. |
includes/class-scanner.php |
The scan engine: resumable job, locking, every check |
includes/class-env.php |
Records the website's PHP (version, server type, php.ini settings, history) |
includes/class-admin.php |
The admin page, AJAX scanning, host message, ignore list, notice |
includes/class-cron.php |
Scheduled scans and email alerts |
includes/class-settings.php |
Settings storage |
includes/class-site-health.php |
Tools → Site Health test |
includes/class-updater.php |
Updates from GitHub releases |
includes/class-cli.php |
WP-CLI commands |
assets/admin.js, assets/admin.css
|
Progress bar, buttons (no jQuery, no external files) |
uninstall.php |
Removes everything on delete |
manifest.json (release zip only)
|
SHA-256 fingerprints of every file, for the self-check |
flowchart TB
subgraph Triggers
UI["Dashboard<br/>Scan now"]
CRON["WP-Cron<br/>weekly / daily"]
CLI["WP-CLI<br/>wp jat-shell scan"]
end
ENV["Website PHP snapshot<br/>class-env.php"]
UI -->|"AJAX: start, then a step every few seconds"| ENGINE
CRON -->|"start, then chained steps"| ENGINE
CLI -->|"steps in a loop"| ENGINE
UI -.->|"records on every admin page"| ENV
subgraph ENGINE["Scan engine — class-scanner.php"]
LOCK["Database lock<br/>one worker at a time"] --> JOB["Job state<br/>wp2ss_job option"]
JOB --> PH["Phases: quick → files → core → plugins → logs"]
SIG["signatures.php<br/>+ wp2ss_signatures filter"] --> PH
end
ENV --> PH
PH --> REPORT["Report<br/>wp2ss_report option"]
REPORT --> ADMIN["Admin page, host message, notice"]
REPORT --> HEALTH["Site Health"]
REPORT --> MAIL["Alert email<br/>scheduled scans only"]
REPORT --> JSON["JSON / CLI output"]
A scan is a resumable job. Instead of one long request (which cheap hosts kill after 30 seconds), the scanner works for a few seconds, saves exactly where it is — down to the position inside a folder — and continues on the next request.
stateDiagram-v2
[*] --> quick : start()
quick : quick — PHP, WordPress, plugins, users, roles, site root, own files, drop-ins, database
files : files — walk wp-content (and outside plugin/upload folders) depth-first
core : core — compare core files with wordpress.org checksums
plugins : plugins — compare wordpress.org plugins with their checksums
logs : logs — PHP error log, debug.log, access log
quick --> files
files --> files : out of time, save place and resume
files --> core
core --> core : out of time, resume
core --> plugins
plugins --> plugins : out of time, resume
plugins --> logs
logs --> logs : out of time, resume at byte offset
logs --> done
done : done — sort findings, save report, fire wp2ss_scan_complete
done --> [*]
One worker at a time. Each slice takes a short database lock. If two browser tabs — or a tab and a scheduled scan — try to work at once, the second waits its turn. A lock left by a crashed request expires after 5 minutes, and a cancelled scan can't be written back by a slice that was still running.
sequenceDiagram
participant T1 as Browser tab 1
participant T2 as Browser tab 2
participant S as Scanner
T1->>S: step
S->>S: lock acquired - work, save
T2->>S: step
S-->>T2: busy, try again shortly
S-->>T1: progress 40%
T2->>S: step (1.5s later)
S->>S: lock acquired - continue from saved place
S-->>T2: progress 55%
Robustness built in
| Situation | What the scanner does |
|---|---|
| A check throws an unexpected error | Reports it as an Info finding and carries on |
| A step could hang (e.g. a host silently dropping connections to wordpress.org) | Attempted at most twice, then skipped with a note |
| A file can't be read | Counted as unreadable; the scan moves on |
| A PHP file over 5 MB | Hashed, and the first and last 1 MB checked (where injected code sits) |
| A folder with hundreds of thousands of files | Read entry by entry; the scan can stop mid-folder; saved state stays tiny |
| Symlinked folders | Followed once each (max 200); loops skipped |
| File names that aren't valid UTF-8 | Stored safely and shown readably — they can't stall the scan |
| The scan pauses for hours (quiet site) | Resumes; only a job with no progress for 24 hours is abandoned |
| Plugin updated mid-scan | Old-format job is discarded; a fresh one starts |
| PHP warnings printed by the host | Can't corrupt the progress bar's replies |
| A badly infected site | At most 1,000 findings stored, plus a count of the rest |
| Database-only attacker | Ignore list and admin baseline are signed with wp-config.php secrets; tampering is reported |
| Attacker edits this plugin | Files checked against the release fingerprints published on GitHub (or WordPress.org's checksums for the WordPress.org edition) |
Nothing about your site is sent to us or any third party. The only outside requests are:
| Request | When | Why |
|---|---|---|
api.wordpress.org — core checksums |
During a scan, at most once a day (cached) | To check core files are untouched. Turn off in settings. |
downloads.wordpress.org — plugin checksums |
During a scan, once a day per plugin version (cached); only the plugin's folder name and version are sent | To check plugin files are untouched. Same setting as core checksums. |
downloads.wordpress.org — this plugin's checksums |
WordPress.org edition: at most weekly (cached) | To check this plugin's own files weren't tampered with |
api.github.com — latest release |
GitHub edition: at most twice a day | To offer plugin updates |
github.com — the release's manifest.json
|
GitHub edition: at most weekly (cached) | To check this plugin's own files weren't tampered with |
github.com — release zip |
GitHub edition: only when you install an update | The update itself |
| Your own hub site — fleet report | After each scan, only if you set up fleet mode | See the fleet dashboard for exactly what's sent |
| Your own hub site — check-in | Hourly, only on fleet members | Asks whether a new scan was requested; sends only the site address |
| Your Slack / Discord webhook | Only on a hub with a webhook set | Fleet alert digests |
WP2SS_DISABLE_UPDATER turns off all three GitHub requests in the GitHub edition. The WordPress.org edition never contacts GitHub.
What it stores (all removed when you delete the plugin):
| Name | Where | Contents |
|---|---|---|
wp2ss_settings |
Options | Your settings |
wp2ss_report |
Options | The latest report |
wp2ss_job |
Options | A scan in progress (deleted when it finishes) |
wp2ss_ignored |
Options | Findings you ignored (signed) |
wp2ss_env |
Options | The website's PHP version, server type, relevant php.ini settings and a short version history |
wp2ss_admins |
Options | Administrator user IDs at the last scan (signed) |
wp2ss_fixes |
Options | One-click fixes you applied, for undo (signed) |
wp2ss_quarantine_folder |
Options | Name of the random, locked quarantine folder in uploads/jat-shell-scanner/ (that folder and its files are deleted too) |
wp2ss_quarantine_dir |
Options | Builds before 1.6 only: the old quarantine folder in wp-content (deleted too) |
wp2ss_fleet |
Options | Fleet mode, hub address and key |
wp2ss_fleet_sites |
Options (hub only) | Latest report from each member site (up to 1,000) |
wp2ss_fleet_status |
Options (members) | When the last report was sent, and any error |
wp2ss_fleet_pins |
Options (hub only) | Each member's registered secret |
wp2ss_fleet_queue |
Options (hub only) | Alerts waiting to go out in the next digest |
wp2ss_fleet_alerts, wp2ss_fleet_daily, wp2ss_fleet_poll
|
WP-Cron | Alert digest, daily overdue check (hub), hourly check-in (members) |
wp2ss_lock |
Options (main site) | Short-lived lock while a slice runs |
wp2ss_release, wp2ss_core_*, wp2ss_manifest_*
|
Transients | Cached update info, checksums and fingerprints |
wp2ss_dismissed |
User meta | Which report's notice you dismissed |
wp2ss_scheduled_scan, wp2ss_scan_step
|
WP-Cron | Scheduled scan events |
On multisite these live in network-wide options.
- PHP2Shell happens below WordPress. The plugin can judge your PHP version and settings and look for traces and leftovers, but it can't watch PHP's memory or processes, and it can't stop the attack. Only updating PHP does.
- Memory leaks leave no trace on disk. If you ran a vulnerable PHP after October 17, 2026, change your secrets even if the scan is clean.
- OS packages can be patched without a version change. The scanner reads Debian/Ubuntu package versions and PHP's build date, but on other systems it can't always tell. Your host can.
- One-click fixes are a first aid kit, not a full clean-up. Quarantining what was found doesn't remove what wasn't; still change your secrets and scan again.
- The fleet hub trusts each member about itself. A fully compromised member can send a fake "clean" report about itself (it can't touch other sites' rows). A site that stops reporting shows as overdue and triggers an alert.
-
Session-file,
/tmpand/etc/ld.so.preloadchecks only work where PHP is allowed to look (many shared hosts block it withopen_basedir); silence there isn't an all-clear. - Known indicators only. Attackers can rename files and rewrite code, so "no signs" means no known indicators — not a guarantee.
- An attacker with full control of the server could tamper with any tool running on it, including this one. The self-check against GitHub or WordPress.org makes that harder, not impossible. For certainty, have your host check from outside.
- It looks for these two attacks specifically. It isn't a general antivirus — keep your usual security plugin or host scanner.
- Finding texts are in English; the interface is translation-ready (
languages/jat-shell-scanner.pot, text domainjat-shell-scanner).
This wiki is built from the repository's README.md by .github/scripts/build-wiki.py. To change a page, edit README.md: edits made here are replaced the next time the wiki is rebuilt.
For site owners
- PHP2Shell and wp2shell
- Install, scan and update
- Understanding your results
- What it checks
- Settings and alerts
- If your site is compromised
- Troubleshooting, logs and hosting
- FAQ
For agencies and developers
- WP-CLI
- Scanning many sites
- How it works, privacy and limits
- Hooks, report format and development
- Sources and license
Links