Three bash scripts to deal with StyleSmuggler — a 0-day RCE affecting Magento / Adobe Commerce. Ref: https://sansec.io/research/stylesmuggler
stylesmuggler-helper.sh— detect an existing compromise and, if found, walk through incident-response cleanup. Run this directly on the server/hosting environment (production, staging, or Cloud node) you want to check — not on your local machine.fix-magento-source.sh— proactively harden a Magento/Adobe Commerce install against the vulnerability while no official Adobe patch exists. Run this on your local development environment (or wherever you edit and commit the Magento source/Composer dependencies) — it patches the source code, which you then deploy through your normal release process.stylesmuggler-remote-check.sh— remotely check a shop's public GraphQL endpoint for exposure/hardening indicators, given just its base URL. Run this from anywhere with network access to the shop — it never touches the server itself, only sends read-only HTTP requests.
Use all three: run fix-magento-source.sh to apply mitigations, run
stylesmuggler-helper.sh periodically (and immediately if you suspect an
incident) to check for IoCs, and run stylesmuggler-remote-check.sh to spot-
check a shop's public GraphQL exposure from the outside — e.g. before and
after applying the perimeter mitigations.
All scripts run on Linux and macOS (e.g. a developer's Mac driving a local
ddev/Warden Magento environment) — CI runs ShellCheck and a functional smoke
test on ubuntu-latest and macos-latest on every push. ps/sed/netstat
usage is written to work with both GNU (Linux) and BSD (macOS) userlands.
Run this on the server environment, not your local machine — it scans the live filesystem, running processes, crontabs, and logs of the host it's executed on, so it must run where the (possibly compromised) Magento/Adobe Commerce install actually serves traffic.
Scans the local system for known Indicators of Compromise (IoCs):
- Running processes (kworker/fc-cache/gvfsd-user masquerading implants)
- Crontabs (current user,
/etc/cron.d, and — with root/sudo — other users' cron spool files) - Filesystem dropper/implant paths in
/tmpand the user's home directory - Magento
var/report/var/logfor injected payload traces and related PHP errors - Active network sockets connecting to known C2 IPs
- Web access logs for exploitation request patterns
If run in an interactive terminal and issues are found, it offers a guided, step-by-step remediation wizard (forensic preservation reminder, cron cleanup, process termination, file quarantine, Redis session invalidation, a credential-rotation checklist, and optional nginx/WAF/php.ini hardening suggestions written to a local file). Every remediation action asks for explicit confirmation before it runs, and nothing is ever applied to live Magento or webserver configuration automatically.
The script never installs, upgrades, or modifies the Magento application itself — it only inspects the OS/filesystem/logs and cleans up OS-level implant artifacts if you confirm each step.
./stylesmuggler-helper.sh [SHOP_DIR] [LOG_DIR]SHOP_DIR— path to the Magento/Adobe Commerce root (default:.)LOG_DIR— path to webserver access logs, file or directory. If omitted, it's auto-detected using theMAGENTO_CLOUD_PROJECTenvironment variable that Adobe Commerce Cloud sets automatically (it holds the project ID): if/var/log/platform/<project-id>exists (Pro Staging/Production), that path is used; otherwise it falls back to/var/log(Dev environments, on-prem, local).
Example:
./stylesmuggler-helper.sh /var/www/magento /var/log/nginxRe-run some checks (cron spool, non-root process scan) as root/sudo to cover other system users, e.g. the webserver account.
On Adobe Commerce Cloud, the writable filesystem lives under /mnt/var
(mounted from /app/var inside the container) and the application code is
deployed read-only under /app. SSH into the environment you want to check
(e.g. magento-cloud ssh -e <environment>), then clone and run the helper
directly from there:
cd /mnt/var
git clone https://github.com/muench-dev/StyleSmuggler.git
cd ./StyleSmuggler
./stylesmuggler-helper.sh /app/mnt/varis writable on Cloud containers, so it's a safe place to clone the repo without touching the deployed/appcode./appis passed asSHOP_DIRso the script scans the actual deployed Magento root.LOG_DIR(2nd argument) is left out on purpose: the script auto-detects it using theMAGENTO_CLOUD_PROJECTenvironment variable that Adobe Commerce Cloud sets automatically on every node (it holds the project ID) — using/var/log/platform/<project-id>on Pro Staging/Production when that path exists, and falling back to/var/logon Dev environments. The script prints whichLOG_DIRit auto-detected. Pass an explicit second argument any time to override this, e.g../stylesmuggler-helper.sh /app /var/log/platform/<project-id>.
Repeat this on each environment/node you want to check (and on each web node if your plan runs more than one), since the scan only covers the local filesystem, processes, and logs of the container it runs on.
Run this on your local development environment, not on a live server — it patches the Magento source code and Composer dependencies (via
composer require,setup:upgrade,setup:di:compile), so it belongs in your normal dev workflow, to be committed and deployed like any other code change rather than applied directly in production.
Applies the community mitigations for StyleSmuggler documented while no official Adobe patch exists yet:
-
Installs and enables the
graycoreio/magento2-style-smuggler-patchComposer module against the Magento install (composer require,module:enable,setup:upgrade,setup:di:compile) — this hardening module blocks the{{block}}directive in email templates, adds strict class validation before instantiation in the grid-row URL generator factory, and breaks open PHP tags inside fatal Web API error reports to prevent log/report poisoning. -
Downloads and applies, via
cweagans/composer-patches, the two real source patches Disrex has published for the root cause:magento/module-email— the front door: the email template preview block renders{{block}}template directives from an unauthenticated request; guarded to admin-area-only, which makes the whole gadget chain unreachable. Disrex calls this the stronger of the two.magento/magento2-base— the sink: the 3 DI-compiler scanner classes thatinclude/require_oncea caller-supplied path, guarded to CLI-only sobin/magento setup:di:compilekeeps working.
Both are fetched from a pinned commit of
disrex-group/stylesmuggler-mitigation(not themainbranch, to avoid a moving-target supply-chain risk) and printed in full for you to review before anything is wired intocomposer.json. Before applying the DI-scanner patch, it also runs the compatibility check from the upstream README: some third-party modules (e.g.mageplaza/module-admin-permissions) callClassesScannerfrom an HTTP-reachable admin controller, and guarding it there would break that admin screen — if such a reference is found, the script warns and asks a separate, explicit confirmation before applying that part of the patch, or lets you skip it and keep only the front-door patch.composer.jsonwiring is done viajqwhen available (with acomposer.json.bakbackup first); otherwise the exact JSON to add by hand is printed.Applying the patches to an already-installed Magento tree isn't a plain
composer install—cweagans/composer-patches2.x doesn't reapply patches to packages that are already installed and matchcomposer.lock. The script instead runscomposer patches-relock(regeneratespatches.lock.jsonfrom theextra.patcheschange) andcomposer patches-repatch(deletes and reinstalls the two patched packages so the patch actually takes effect), falling back tocomposer update magento/module-email magento/magento2-basefor oldercweagans/composer-patchesversions that don't have those commands. It then runscomposer update --lock— editingcomposer.json'sextrakey directly makescomposer.lock's content-hash stale (Composer hashesextratoo), so without this a latercomposer install— including in CI or an Adobe Commerce Cloud deploy — would warn or fail on an out-of-date lock file. -
Writes webserver/WAF/php.ini/OS hardening suggestions (Cloudflare WAF rule, nginx query-string filter, optional
/graphqlendpoint block,disable_functionsfor PHP,noexecmount advice for/tmp,/var/tmp,/dev/shm) to a localstylesmuggler-fix-snippets.txtfile for manual review.
Every state-changing step asks for explicit y/N confirmation first.
Steps 1–2 actually execute Composer/bin/magento commands against the
Magento root once confirmed. Step 3 never edits live nginx/php.ini/
fstab configuration or restarts any service — it only ever writes a local
snippet file for you to apply yourself.
This script does not detect or clean up an existing compromise — use
stylesmuggler-helper.sh for that. Administrators must also note the
Graycore module is a hardening measure, not a fix for the structural root
cause in the DI compiler, and does not remove any backdoor that may
already be present. Validate on a staging system before production.
The script auto-detects local dev environments and runs Composer/bin/magento
through them instead of directly on the host:
- ddev — detected via a
.ddevdirectory inMAGENTO_ROOT; runsddev composer ...andddev exec bin/magento .... - Warden — detected via
WARDEN_ENV_NAME=inMAGENTO_ROOT/.env; runswarden env exec -T php-fpm composer ...andwarden env exec -T php-fpm bin/magento .... Override the service name withWARDEN_PHP_SERVICEif your project doesn't usephp-fpm. - Otherwise falls back to plain native
composer/bin/magentoon the host.
If a project marker is found but the matching CLI (ddev/warden) isn't on
PATH, the script warns and falls back to native. Force a specific mode
with FIX_MAGENTO_ENV=ddev|warden|native if detection picks the wrong one.
Every confirmation prompt shows the exact resolved command (e.g.
Run: ddev composer require ... ?) before it runs.
./fix-magento-source.sh [MAGENTO_ROOT]MAGENTO_ROOT— path to the Magento/Adobe Commerce root (default:.)
Example:
./fix-magento-source.sh /var/www/magento
# Force a mode instead of auto-detecting:
FIX_MAGENTO_ENV=warden WARDEN_PHP_SERVICE=php ./fix-magento-source.sh /var/www/magentoRun this from anywhere with network access to the shop — it only sends a handful of read-only HTTP requests to the shop's public base URL and its
/graphqlendpoint; it never logs into or touches the server itself.
Sansec's writeup discloses IoCs (e.g. POST /graphql?styles[...]=... as an
access-log signature) but does not publish the full request/mutation
needed to actually trigger the underlying DI-compiler gadget chain, and no
official Adobe patch/CVE existed while this script was written. That means
there is no reliable way to remotely confirm or rule out the RCE itself
from outside the server.
What the script does instead is check exposure and mitigation-status
indicators — things the community mitigations in fix-magento-source.sh
and stylesmuggler-hardening.txt either reduce or block:
- Whether the shop and its
/graphqlendpoint are reachable at all - Whether GraphQL introspection is enabled (widens the attack surface)
- Whether the published
styles[...]query-string attack signature is filtered at the edge (WAF/nginx) or passes straight through — using only an inert marker value, never a template/code payload. Since backends disagree on what a plain, unfiltered GraphQL request returns (200,400,501, ...), this compares the probe's response against a plain baseline request instead of a fixed list of "blocked" status codes: an identical status means nothing filtered this signature, a different one means something rejected it specifically (most likely a WAF/nginx rule, though a strict request parser could produce the same effect) - Whether
/graphqlhas been disabled entirely at the webserver (the strongest documented mitigation for non-headless storefronts) - Whether
/paypal/transparent/response/(used in the published attack chain's second stage) is reachable — informational only, since it's normal Magento functionality on stores with PayPal enabled, not itself a vulnerability
A "no risk indicators found" result is not proof the shop is
unaffected — it only means these specific exposure/mitigation checks found
no issue. It doesn't replace stylesmuggler-helper.sh (local IoC/compromise
scan) or fix-magento-source.sh (source-level mitigation), and it never
sends any payload capable of triggering code execution.
./stylesmuggler-remote-check.sh <SHOP_BASE_URL>SHOP_BASE_URL— the shop's public base URL (scheme optional, defaults tohttps://if omitted)
Environment overrides:
STYLESMUGGLER_TIMEOUT— per-request curl timeout in seconds (default10)STYLESMUGGLER_INSECURE=1— passcurl -kto skip TLS certificate verification
Example:
./stylesmuggler-remote-check.sh https://www.example.comExit codes: 0 no risk indicators found, 1 one or more found, 2 usage
error (missing URL), 3 curl not found, 4 shop unreachable.
Both scripts are provided as is, without warranty of any kind. Any cleanup, remediation, or hardening action you perform is entirely at your own risk — always review the findings and each confirmation prompt carefully before proceeding.