storetown-media / module-shop-guard
Hardening for Magento 2: blocks StyleSmuggler, PolyShell and TrojanOrder requests, bans attacking IPs and abusive bots automatically, loads new signed attack patterns every hour, watches pub/, var/report, running processes and every code file against a reference state for backdoors
Package info
github.com/storetown-media/stm-magento-2-shop-guard
Type:magento2-module
pkg:composer/storetown-media/module-shop-guard
Requires
- php: ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0
- magento/framework: ^103.0
- magento/module-admin-notification: ^100.4
- magento/module-backend: ^102.0
- magento/module-catalog: ^104.0
- magento/module-config: ^101.2
- magento/module-cron: ^100.4
- magento/module-customer: ^103.0
- magento/module-email: ^101.1
- magento/module-store: ^101.1
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Free hardening module for Magento Open Source and Adobe Commerce 2.4.4 or later, PHP 8.1 – 8.5 (tested on 2.4.8-p4 with PHP 8.4). It bundles what we run on our customers' shops after the 2026 attack waves (PolyShell, SessionReaper, StyleSmuggler, PHP2Shell) into one small module.
Video
Every protection in 2:37 minutes, shown on the real Magento backend: English on YouTube · Deutsch auf YouTube
What it does
Request filter — runs before Magento routes a request, so blocked requests cost almost nothing.
| Rule | Blocks |
|---|---|
| StyleSmuggler (APSB26-146) | template_styles, styles[with_resolved], styles[…generatorClass…], rowUrl=$this in query or body, also via /paypal/transparent/response/ |
| TrojanOrder | template directives {{var …}}, {{block …}} … in customer input |
| PHP code | <?php, <?=, eval(base64_decode(…)) in parameters, body or user agent |
| CosmicSting | XML entities with SYSTEM |
| PHP2Shell | upload progress fields (PHP_SESSION_UPLOAD_PROGRESS), never used by Magento |
| PolyShell (APSB25-94) | base64 file uploads into cart items via REST |
| Address uploads | /customer/address_file/upload, used to drop files for SessionReaper |
| Scanner sign-ups | e-mail addresses on reserved domains (.invalid, .test, .example) |
| Bots | SEO tool crawlers and AI training crawlers (two switches), your own lists |
| GraphQL | optional emergency switch |
| Filter crawls | optional: layered navigation URLs only with a live session or internal referrer |
Search engines, AI search agents, link previews, price comparison and uptime monitors are never blocked by agent. The admin area and API calls with a token are only subject to the IP lists.
Attack patterns from Storetown Media (hourly) — new attack waves are filtered before the next module update.
The shop fetches https://www.storetown-media.de/shop-guard/rules.json with its Ed25519 signature; a list that does
not verify against a key built into the module is never used. Before a rule becomes active the shop checks it itself:
it must compile and must not match any of ten ordinary requests (start page, search, cart, checkout, account, contact
form …), and every pattern runs with a backtracking limit. New rules usually start in the mode log: they only count
hits, which bin/magento shopguard:rules shows, and block later. An older list never replaces a newer one.
Only the file is fetched, nothing about the shop is sent; storetown-media.de sees the IP address of the server.
Switch: Load new attack patterns from Storetown Media. Test: https://your-shop/?shopguard-feed-test answers 403.
Password guessing on the admin login — Magento locks an account after a few failures, but the address keeps
trying other accounts. From 10 failed admin logins within 15 minutes (adjustable) the address is blocked until the end
of the next day. Addresses that logged in to the admin in the last 30 days, Never block these IPs and proxy
addresses are never blocked. The block appears under Automatically blocked IPs and in var/log/shop_guard.log.
Files anyone can download (daily) — the shop requests itself the way an attacker would: app/etc/env.php, .git/, database dumps and backups found in the shop root or pub/, phpinfo, Adminer, phpMyAdmin, var/log/*.log and composer.json (a web root on the Magento main folder instead of pub/). A finding needs the content to prove it, so a "page not found" with status 200 never alerts. If the shop cannot reach itself (firewall), the check stays silent.
Early warnings (critical and high) — the signed feed can carry security warnings with the conditions under which a shop is affected (Magento version, PHP version of the web server, Composer packages, enabled modules). The shop checks them itself and sends critical and high warnings that apply once by mail and to the admin inbox (hourly at :41). bin/magento shopguard:rules shows every warning and whether it affects the shop. Shop Security Guard Pro alerts every level itself, with a task in the dashboard and the webhook; a warning Pro has sent is not sent twice.
Card test brake — card testers run stolen card numbers through the checkout; every single request looks like a customer paying, only the number tells. Payment attempts are counted per address and hour (REST payment-information, GraphQL placeOrder, checkouts with placeOrder in the address). Above the limit (8) the address is only noted in the default mode Watch only; bin/magento shopguard:cardtests lists those addresses. In the mode Refuse further payment attempts they get 403 and the bot scan bans them. Admin addresses and proxies are never counted.
Upload block — three plugins stop custom option file uploads before they reach the disk (frontend validator, inner validator and the REST processor that PolyShell uses). Switched on at install time only if the catalog has no options of type File.
Bot scan (hourly) — evaluates a sample of the requests and writes its results into the configuration, each entry with an expiry date:
- IPs that sent an attack pattern
- bot and HTTP library user agents above the hourly threshold
- outdated browser strings above the threshold (rotating fake agents of scraper networks)
- fake Googlebot/Bingbot/Applebot: reverse and forward DNS must match
- single IPs above the hourly threshold (one day)
It never bans: IPs on the allow list, addresses admins logged in from (30 days),
private and server addresses, and while REMOTE_ADDR is used, any address carrying over 30 % of the traffic
(that is usually the proxy in front of the shop).
Backdoor scan (every 30 minutes) — silent while nothing changes, mail and admin inbox on findings:
- PHP files,
.user.ini/php.ini, PHP-enabling.htaccessand session files inpub/mediaandpub/static - PHP code disguised as images
- PHP code that refused REST uploads left in the system temp directory (PolyShell attempts)
- unknown PHP files in
pub/and all its subdirectories (allow list with optional SHA-256), obfuscated code inpub/index.php - PHP code hidden in files with other extensions (
.mp4,.tif,.m3u8, ...) anywhere inpub/ - directories the shop user cannot open, so a locked directory is reported instead of passing as clean
- attack code in
var/reportand the main logs (StyleSmuggler stage 1), report guard line of the Adobe hotfix respected - processes of the shop user disguised as kernel threads, started from
/tmpor deleted files, known miners - crontab entries that download and run code
- obfuscated scripts and template injections in CMS blocks, pages, e-mail templates and configuration
- new admin users and integrations with API access (reported for 7 days)
- every code file in
vendor,app,lib,pub,setupandbinagainst a reference state: new, changed and deleted files, package updates (fromvendor/composer/installed.json) reported as such - webshell tricks in code files: encoded eval, hidden includes, goto obfuscation, known webshells
File names are checked on every run, file contents only if changed (weekly in full).
Integrity check — the first scan writes a reference state of all code files (SHA-256) to
var/shop_guard/integrity.json.gz, its checksum goes into the database. Before that, every code file passes the
signature check, so an infection already present is reported instead of being taken over. Later runs hash only
files whose size, ctime or mtime moved (ctime cannot be set back with touch), the weekly run hashes everything.
After a deployment or update take the new state with bin/magento shopguard:integrity --accept.
generated/ is not covered, it changes with every compilation.
Hardening — writes pub/media/.user.ini and pub/static/.user.ini with engine = Off
(PHP-FPM then refuses to run uploaded scripts there, also on nginx). Existing files are never overwritten.
System check — on the configuration page and in the alert mail:
- PHP version against PHP2Shell (CVE-2026-109618, CVE-2026-109619; fixed in 8.2.35, 8.3.36, 8.4.27, 8.5.12)
session.upload_progress.enabled,fatal_error_backtraces,allow_url_include,display_errors,disable_functionsas seen by PHP-FPM, not by the CLI- StyleSmuggler hotfix: tests whether
BlockFactoryrefuses a non-block class before creating it - admin path, two-factor authentication
- missing Adobe security patches via
vendor/bin/patch-status, if installed (daily)
PHP2Shell is triggered while PHP parses the request, before any Magento code runs. No Magento module can block it. Update PHP. Until then set
session.upload_progress.enabled = 0.
What to do — every finding comes with a short, concrete instruction: in the admin, in the alert mail, in the admin inbox and on the CLI. The configuration page starts with the result in plain words.
STM Shop Security Guard Pro (paid, separate module) adds a dashboard under System › Security › Shop Security Guard: backdoor and bot scan at the push of a button, checked areas, findings with advice, attack and bot statistics of the last 14 days, latest automatic blocks, findings marked as harmless with a reason, code changes accepted per file or package, admin users and integrations with last login, 2FA and rights, and a log of who did what.
Installation
composer require storetown-media/module-shop-guard bin/magento module:enable Storetown_ShopGuard bin/magento setup:upgrade bin/magento setup:di:compile # production mode bin/magento setup:static-content:deploy # production mode bin/magento cache:flush
Or copy the module to app/code/Storetown/ShopGuard and run the same commands.
Magento cron must be running.
After installation
- Stores › Configuration › STM › STM Shop Security Guard
- Check Your IP as seen by the shop. Behind a proxy, Varnish or CDN set Client IP from to the header that shows your real IP. Until then IP blocking stays inactive.
- Enter your office and ERP addresses under Never block these IPs.
- Enter a recipient under Notifications.
- Run the first scan:
bin/magento shopguard:scan --full - Test the request filter: open
https://your-shop/?shopguard-test— Shop Security Guard answers with status 403 and "Shop Security Guard ist aktiv".
Commands
bin/magento shopguard:scan [--full] [--notify] # system check and backdoor scan bin/magento shopguard:bots [--dry-run] # bot scan over the collected traffic bin/magento shopguard:integrity [--accept] # compare code with the reference state, or take the current state bin/magento shopguard:rules [--update] # attack patterns from Storetown Media: state, hits, fetch now bin/magento shopguard:cardtests # addresses over the payment attempt limit, last 7 days
Files
| Path | Content |
|---|---|
var/log/shop_guard.log |
every blocked attack |
var/shop_guard/traffic.log |
request sample, emptied by the hourly bot scan |
var/shop_guard/runtime.json |
PHP settings of the web server, refreshed every 6 hours |
var/shop_guard/integrity.json.gz |
reference state of the code files, checksum in the database |
var/shop_guard/feed.json, feed-state.json |
verified attack patterns from Storetown Media and the state of the last fetch |
var/shop_guard/feed-hits.log |
hits of patterns in the mode log |
var/shop_guard/login-failures.json |
failed admin logins per address in the last 15 minutes |
var/shop_guard/card-attempts.json, card-tests.log |
payment attempts per address in the last hour, addresses over the limit |
Limits
- Pages delivered by Varnish or a CDN never reach PHP and are not filtered.
- The module complements patching and a WAF, it does not replace them.
License
MIT, © Storetown Media, Tornesch — storetown-media.de

