AMWScan: a PHP antimalware scanner that reports before it deletes
AMWScan (PHP Antimalware Scanner) is a free tool to scan php files and analyze your project to find any malicious code inside it.
At a glance
- What is it?
- AMWScan scans PHP projects and WordPress installations for suspicious code, known malware patterns and files whose contents no longer match the upstream release. Its most useful property is the order of operations: a report only mode that touches nothing, four levels of scanning, and an explicit warning that a detection does not prove a whole file is malicious.
- Who is it for?
- AMWScan suits a site owner or a hosting team that suspects a PHP compromise and needs to know what changed and where the suspicious code sits, before touching anything on a production server. It is the wrong choice as your only defence, because it inspects what is on disk after the fact rather than preventing the write, and it is the wrong choice for a codebase you cannot afford to have quarantined, since the remediation actions it offers are deletions.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 20 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Report first, remediate later
AMWScan, the PHP Antimalware Scanner, is a free tool to scan PHP files and analyse a project to find malicious code inside it. The current version is 0.21.15 and the homepage is the project documentation site, with a Docker Hub image, a GitHub Action in the marketplace called php-antimalware-security-scan, and a WordPress plugin called amwscan published on WordPress.org. The documentation is available in English, Italian, German, French, Spanish, Russian, Simplified Chinese, Japanese, Hindi and Arabic.
The design decision that shapes everything is the order of operations. The project leads with a section called Start with a safe report, and the sequence is deliberate: download the standalone scanner, confirm its version, then inspect a project without changing files.
curl --fail --silent --show-error --location \
--output scanner \
https://raw.githubusercontent.com/marcocesarato/PHP-Antimalware-Scanner/master/dist/scanner
php scanner --version
php scanner /var/www/example --lite --report-onlyThe scanner writes scanner-report.html in the current directory, and the instruction is to review each finding in context before cleaning, quarantining, or deleting anything.
Behind that instruction sits a caution that is worth repeating in your own runbook. Create a current backup before using file actions. A detection identifies suspicious code. It does not prove that the entire file is malicious.
That is the right framing for a scanner that runs on a live server. A file with one suspicious string in it may be a compromised upload, and it may also be a library that looks like an attack. The report is evidence for a human decision, and the tool is built to keep the human in the loop until they choose otherwise.
Four levels of scanning, from broadest to narrowest
The mode table is the tool's main dial, and each mode exists because a different question needs a different answer.
Full, with no mode option, is for the broadest incident review. Lite, with --lite or -l, is the practical first scan and omits noisy shell output signatures. Signatures, with --only-signatures or -s, does focused checks for known malware patterns. Exploits, with --only-exploits or -e, audits suspicious code structures. Functions, with --only-functions or -f, reviews dangerous PHP calls.
The Lite mode is the one to understand first, because it is where the tuning work shows. It omits the shell output signatures that produce most of the noise in a real codebase, and it demotes several heuristics from a finding to an informational exploit candidate. Alignment, long concatenations, encoded function names such as hex_char, escaped include paths and loader or reconnaissance proximity heuristics produce informational exploit-candidate findings in Lite mode instead of marking a file infected. The execution rule also treats decoded callback names as candidates unless the match is strong.
That is a deliberate accuracy trade. A scanner that marks every obfuscation as malware is a scanner people disable, and a scanner that is quiet on a real attack is worse than useless. The Lite mode sits between the two, and the Full mode is there when you have the time to read every finding.
The project also documents how the scanner reduces false positives in the other modes. Full and Lite scans distinguish short quoted paths from long obfuscated strings and exclude binary data inside PHP strings and comments from the non_printable heuristic, which avoids false positives in PDF and cryptography libraries without trusting files by their path. Request driven Base64 data: and data:// includes are detected, including concatenated and interpolated request values, while plain URI assignments, static Base64 text and literal variable names in single-quoted strings do not trigger the base64_inclusion rule.
What the eight check families look at
The check table is the compact description of the scanner's scope, and it covers more than pattern matching.
Malware signatures finds known code fragments, regular expressions, hashes and suspicious domains. JavaScript applies scoped JavaScript malware definitions and dangerous domain checks to .js files by default, which matters for a site whose front end ships compiled assets. Exploit patterns flags code structures that attackers often use.
Framework processes associates confirmed process APIs from major PHP frameworks and platforms with their owner, and this is the check that carries the most design effort.
Dangerous functions identifies calls such as system and shell_exec for review. Platform integrity compares supported applications and packages with trusted release checksums, which is the check that finds a modified core file rather than a suspicious string. ZIP archives inspects bounded archive content when --scan-archives is enabled, so a backup archive dropped in the web root is looked at too. WordPress database inspects triggers, administrators and orphaned posts, with confirmed local remediation for what it finds.
Put together, the eight cover the three places a PHP compromise usually shows up: a file that was added, a file that was changed, and a file that talks to the operating system. A scanner that only greps for known strings will miss the first two cases, and one that only compares checksums will miss a backdoor in a third party plugin whose upstream release is also compromised or absent.
Framework process detection and the cases it refuses to report
The framework process check is worth reading closely, because its limitations are stated with unusual precision.
Framework process findings run in Full, Lite, Exploits and Functions modes, but not in Signatures-only mode. They use PHP token analysis, and they require both exact framework provenance and a terminal execution method. Coverage includes Symfony Process, the legacy ProcessBuilder and process-message dispatch, Laravel Process, WordPress SSH and WP-CLI, Magento and Adobe Commerce, Drupal and Drush, Joomla, PrestaShop, TYPO3, CodeIgniter, Yii and CakePHP.
What is deliberately not reported is as important. A generic Process class is not reported. A bare run() or execute() call is not reported. Framework constructors or builders without execution are not reported. The reasoning is that these are ambiguous: a Process class could belong to anything, and a builder that is never executed is not a compromise.
The WooCommerce case is handled explicitly. It has no verified product-owned operating system process API, so its WordPress and WP-CLI paths remain covered while in-process Action Scheduler work is excluded.
The severity model is also conservative in a useful way. Ordinary confirmed execution remains warning-level review evidence. A command literal connected to that execution is promoted to dangerous only for high-confidence behaviour such as download-to-interpreter pipelines, encoded interpreter execution, reverse shells, or destructive root deletion.
For a reader building a policy around this tool, the shape is clear. The scanner is trying to be right rather than loud, and its report is designed to survive contact with a human reviewer who knows what their framework code looks like.
Integrity checks against upstream releases
The integrity side of the scanner compares your files against what the upstream project published, and the documentation is precise about how it decides what to compare.
Normal code scans work offline with embedded definitions. Platform checksums and Maltrail definition updates can require outbound HTTPS, which is worth knowing before you run the scanner on an isolated host or inside a locked down network.
Dedicated integrity verifiers cover WordPress, WooCommerce, Drupal, Joomla, Magento and Adobe Commerce, PrestaShop, TYPO3, Laravel, Symfony, CodeIgniter, Yii and CakePHP. The framework verifiers validate only their owned locked Composer packages against official distributions, so the comparison is scoped to what that framework is responsible for. WooCommerce is the exception and uses official WordPress plugin checksums.
Two operational details are documented and both matter in practice. Successful remote data is cached outside the scanned project, so a large WordPress installation is not re-fetched on every run. And failed WordPress checksum requests are retried after one hour, which is a sensible fallback for a network hiccup rather than treating an unreachable host as a mismatch.
There is a subtlety about paths. Integrity manifest paths remain anchored to the detected installation root, including when a root directory name is repeated in a managed filename, which stops a file in a nested directory being matched against the wrong installation.
And there is a nice efficiency property: when every eligible file passes checksum verification, AMWScan skips the supplemental component-inventory and reputation checks. A clean installation therefore costs much less work than a suspicious one.
Unattended runs skip the prompts, and that is deliberate
The behaviour of the tool in a non-interactive environment is documented in detail, and it is the section to read before putting AMWScan in a cron job.
Without an interactive standard input, for example in cron, in CI, or in Docker without the -t flag, the scanner skips prompts and leaves files requiring a decision unchanged. It ignores piped answers. The --silent option also skips prompts, even in a terminal.
Explicit actions such as --auto-delete and --auto-prompt still apply, and --report-only is the way to prevent remediation entirely.
The result is a sane default. A scheduled run will not quietly delete files because a script fed it input on standard input, and it will not hang waiting for a keypress that will never come. But it also means that a scheduled run with --auto-delete will act, and that is the configuration most worth reviewing before it goes anywhere near a production web root.
The packaging options cover the same ground from different angles. There is a Docker image built from php:8.5-cli-alpine that installs the scanner as a standalone binary with a two gigabyte memory limit and the php entrypoint, a GitHub Action for the marketplace, and a WordPress plugin that runs the same checks inside an admin interface.
The project is GPL-3.0 licensed, with the licence file at the root, the primary language PHP with a requirement of 7.4 or newer, and the repository carries a CHANGELOG, a TESTING.md, a phpunit configuration and a corpus configuration alongside the source, the definitions directory and the bundled test files.
Editorial conclusion
AMWScan suits a site owner or a hosting team that suspects a PHP compromise and needs to know what changed and where the suspicious code sits, before touching anything on a production server. It is the wrong choice as your only defence, because it inspects what is on disk after the fact rather than preventing the write, and it is the wrong choice for a codebase you cannot afford to have quarantined, since the remediation actions it offers are deletions. Verify three things before running it with any action enabled: that you have a current backup, since the documentation asks for one before using file actions, that the report you get in report-only mode is one your team knows how to read, and that the findings on a clean installation of the same stack are empty, because a tool that flags your own framework code is going to be ignored.
Frequently asked questions
What is AMWScan, the PHP Antimalware Scanner?
It is a free tool to scan PHP files and analyse a project to find malicious code inside it, covering known malware signatures, exploit patterns, dangerous function calls, framework process usage, platform integrity against upstream checksums, ZIP archives and WordPress database objects. It is GPL-3.0 licensed and works with PHP 7.4 or newer.
How do I run a first scan that does not change anything?
Download the standalone scanner, confirm it with php scanner --version, then run php scanner /var/www/example --lite --report-only. The scanner writes scanner-report.html in the current directory, and the documentation asks you to review each finding in context before cleaning, quarantining or deleting anything.
Which scanning modes does AMWScan offer?
Five: Full with no mode option for the broadest incident review, Lite with --lite or -l for a practical first scan that omits noisy shell output signatures, Signatures with --only-signatures or -s, Exploits with --only-exploits or -e, and Functions with --only-functions or -f. Framework process findings do not run in Signatures-only mode.
Will AMWScan delete files when I run it in cron or CI?
Not by default. Without an interactive standard input the scanner skips prompts and leaves files requiring a decision unchanged, it ignores piped answers, and --silent skips prompts even in a terminal. Explicit actions such as --auto-delete and --auto-prompt still apply, and --report-only prevents remediation, so you should take a current backup before enabling any file action.
Does AMWScan need an internet connection?
Normal code scans work offline with embedded definitions. Platform checksums and Maltrail definition updates can require outbound HTTPS, successful remote data is cached outside the scanned project, and failed WordPress checksum requests are retried after one hour.
Which frameworks does the AMWScan process check recognise?
It covers Symfony Process and the legacy ProcessBuilder, Laravel Process, WordPress SSH and WP-CLI, Magento and Adobe Commerce, Drupal and Drush, Joomla, PrestaShop, TYPO3, CodeIgniter, Yii and CakePHP. A generic Process class, a bare run() or execute() call and framework constructors without execution are deliberately not reported.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/marcocesarato-php-antimalware-scanner)Was this useful?