Skip to content
MAGEKWIKScanner
high

Content-Security-Policy for Magento 2: Report-Only vs Restrict, and Tuning to Stop Skimmers

Magekwik Security 5 min read

HARDENING HIGH Magento / Adobe Commerce scan.magekwik.com

Magento_Csp ships most storefront pages in report-only mode, which enforces nothing. This is how report-only and restrict actually differ, why an over-broad whitelist still lets skimmers run, and the exact sequence to tune csp_whitelist.xml and flip to restrict without breaking checkout.

What it is

The Magento_Csp module (shipped since 2.3.5) assembles and emits the Content-Security-Policy and Content-Security-Policy-Report-Only response headers. It runs in one of two modes, set independently for the storefront and admin areas. In report-only mode the browser evaluates the policy, logs every violation to the configured report-uri/report-to endpoint, and enforces nothing. In restrict mode the browser refuses to load or execute any resource the policy does not permit.

The effective policy is built at runtime by merging every enabled module's etc/csp_whitelist.xml with the per-area mode flags in etc/config.xml. Since 2.4.7, restrict mode is the default only for payment pages (storefront and admin); every other page defaults to report-only. Before 2.4.7, the entire application was report-only. That default is the crux of the problem.

Root cause

There are two independent failure modes, and most exposed stores have both.

First, report-only is telemetry, not a control. A Content-Security-Policy-Report-Only header blocks nothing. A large share of stores run report-only across the whole storefront, never process the reports, and believe the presence of a CSP header means they are protected. It does not.

Second, even in restrict mode the shipped policy is intentionally permissive so third-party integrations do not break on upgrade. Directives fall back to broad host lists, and inline execution is frequently allowed through 'unsafe-inline' or an over-wide script-src. A policy that permits inline script or wildcards a payment/analytics origin is satisfied by an injected inline handler or a skimmer served from an already-whitelisted host. CSP is only as strong as the smallest source set you can get the store to run on.

How attackers abuse it

A web skimmer needs two capabilities the browser normally grants freely: execute attacker JavaScript in the checkout document, and exfiltrate the captured card data to an attacker-controlled host. A correctly restricted CSP removes both.

  • script-src without 'unsafe-inline' (nonce- or hash-based) blocks inline event handlers and injected inline <script> blocks outright.
  • connect-src/default-src and form-action constrained to your own origins block the exfiltration beacon (fetch, XMLHttpRequest, navigator.sendBeacon, or a rogue form POST).

Report-only defeats all of this silently: the browser dutifully files a violation report while the skimmer runs and the card data leaves. An over-broad whitelist defeats it too, because the injected code either runs inline or reuses a domain you already trust.

Report-only is not mitigation

A store running the storefront in report-only mode has the same skimmer exposure as a store with no CSP at all. The only difference is that violations are logged after the data has already been exfiltrated.

Who got hit / real examples

In April 2026 Sansec documented a campaign that hid a skimmer inside a 1x1 SVG element on 99 Magento storefronts. The entire payload was base64-encoded inside the SVG's onload handler, decoded with atob() and detonated via setTimeout. It intercepted checkout clicks with useCapture, presented a fake "Secure Checkout" overlay, harvested card and billing fields, then exfiltrated to one of six domains (all resolving to 23.137.249.67) via a /fb_metrics.php endpoint masquerading as Facebook analytics. An inline onload handler and an off-origin beacon are exactly what a nonce-based script-src and a tight connect-src refuse.

The upstream supply of such compromises is well understood. CosmicSting (CVE-2024-34102, CVSS 9.8, NVD-published 2024-06-13, added to CISA KEV 2024-07-17) let unauthenticated attackers read app/etc/env.php, recover the encryption key, and then rewrite CMS blocks through the Magento API to plant JavaScript. Sansec tracked waves compromising 2,000-plus and later 3,000-plus stores. CSP does not stop the initial file read, but a restricted policy caps the blast radius of the injected script that follows.

How to check

Inspect the live headers on a checkout URL and confirm which header name you actually get back.

Distinguish enforced CSP from report-only
curl -sSI https://store.example.com/checkout/ | grep -i 'content-security-policy'
# content-security-policy-report-only: ...   -> ENFORCES NOTHING
# content-security-policy: ...               -> enforced (restrict)

Then confirm the configured mode per area and review the whitelist surface you are trusting.

Read the effective mode and audit whitelisted hosts
php bin/magento config:show csp/mode/storefront/report_only   # 1 = report-only, 0 = restrict
php bin/magento config:show csp/mode/admin/report_only

# Every host you are trusting, across all modules:
grep -rho 'type="host"[^>]*>[^<]*' app/ vendor/ --include=csp_whitelist.xml | sort -u

In restrict mode, the admin's CSP violation report grid (and your report-uri collector) surfaces legitimate resources that still need whitelisting before you tighten further.

How to fix it

The safe sequence is deliberately report-only first, then flip to restrict once the policy is clean. Do not skip the collection phase or you will break checkout.

  1. Stay in report-only, drive real checkout/admin traffic, and collect violations from your report-uri endpoint until the report stream is quiet.
  2. Add only the hosts you genuinely require to a module etc/csp_whitelist.xml, scoped to the narrowest directive. Remove leftover analytics/tag-manager origins you no longer use.
app/code/Vendor/Security/etc/csp_whitelist.xml — scope every host to one directive
&lt;csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp:etc/csp_whitelist.xsd"&gt;
    &lt;policies&gt;
        &lt;policy id="script-src"&gt;
            &lt;values&gt;
                &lt;value id="psp" type="host"&gt;https://js.your-psp.com&lt;/value&gt;
            &lt;/values&gt;
        &lt;/policy&gt;
        &lt;policy id="connect-src"&gt;
            &lt;values&gt;
                &lt;value id="psp-api" type="host"&gt;https://api.your-psp.com&lt;/value&gt;
            &lt;/values&gt;
        &lt;/policy&gt;
    &lt;/policies&gt;
&lt;/csp_whitelist&gt;
  • Render any store-owned inline script/style through $secureRenderer so it is nonce-covered rather than blanket-allowed with 'unsafe-inline'. Prefer nonces/hashes over host wildcards.
  • etc/config.xml — flip both areas to restrict once the policy is clean
    &lt;default&gt;
        &lt;csp&gt;
            &lt;mode&gt;
                &lt;storefront&gt;&lt;report_only&gt;0&lt;/report_only&gt;&lt;/storefront&gt;
                &lt;admin&gt;&lt;report_only&gt;0&lt;/report_only&gt;&lt;/admin&gt;
            &lt;/mode&gt;
        &lt;/csp&gt;
    &lt;/default&gt;

    After the flip, keep the report-uri collector running. In restrict mode those reports are now your intrusion signal: an unexpected connect-src or inline script-src violation on checkout is a skimmer being blocked in real time, not a tuning task.

    Order of operations

    Never set report_only to 0 before the report-only stream is quiet. The correct failure mode during tuning is a logged violation, never a broken checkout for a paying customer.

    References & sources

    Primary sources — advisories, vendor research, and the CVE record. Verify against these, not us.

    1. Adobe Commerce: Content security policies (developer docs)
    2. Sansec: SVG Onload Tag Hides Magecart Skimmer on 99 Stores
    3. Sansec: CosmicSting attack & defense overview (CVE-2024-34102)
    4. NVD: CVE-2024-34102
    5. CISA Known Exploited Vulnerabilities Catalog
    MALWARE & SKIMMERS CRITICAL Magento / Adobe Commerce scan.magekwik.com
    critical Malware & skimmers

    Backdoored and Abandoned Extensions: Supply-Chain Compromise of the Magento Ecosystem

    Commercial Magento extensions run with core-level privilege on every request, which makes their license-check files an ideal home for a dormant backdoor. We dissect the 2025 Tigren/Meetanshi/MGS compromise, the 2022 FishPig/Rekoobe breach, and the polyfill.io skimmer — plus the exact grep and CSP checks to find and shut them down.

    5 min read

    ← All security posts