Chargebacks & Fraud

A Skimmer Hidden in Google Tag Manager Used Stripe’s Own API to Store Stolen Cards

Sansec documented a Magecart skimmer that loads from real Google Tag Manager containers and stores Magento checkout data as fake Stripe customers. Why a CSP allowlist misses it, what PCI DSS 6.4.3 and 11.6.1 expect, and what to check.

A night watchman and a young developer catch a raccoon slipping an envelope into a general store's mail slot on a foggy late-October night.

A card skimmer documented by the e-commerce security firm Sansec never sends a byte to a server its operators run. It loads from Google Tag Manager, downloads its payload from a customer record in Stripe, and uploads stolen card numbers back to Stripe as new customers. Sansec’s Forensics Team described the campaign on June 4, 2026, and its explanation of why the traffic goes unnoticed is blunt. “Stores allow that domain by default,” Sansec wrote of api.stripe.com, “so the skimmer slips past Content Security Policy rules and network filters that would otherwise flag traffic to an unknown skimmer domain.”

The code targets Magento and Adobe Commerce checkouts. The lesson reaches further, to any store that treats a list of approved domains as its main defense for payment pages.

How the skimmer loads and where it gets its code

According to Sansec, the loader sits in real Google Tag Manager (GTM) containers as a custom tag, so it is served from googletagmanager.com like any analytics or ad tag. The report names four container IDs, GTM-P6KZMF63, GTM-55976FLP, GTM-MSDHV3HG, and GTM-TV4CSHVN. The report does not say how those containers got onto the affected stores, though its recommendations call GTM containers and “external script” settings “a common injection point.”

On any page whose address contains “checkout,” the loader waits two seconds and then requests a single Stripe customer record, cus_TfFjAAZQNOYENR. The skimmer’s code is split across that record’s metadata fields, labeled meta0, meta1, and so on. The loader joins the pieces and runs them with JavaScript’s new Function() constructor. Sansec reports that the customer record was created on December 24, 2025, using Stripe’s own sample data, and says the campaign has likely been running since at least late last year.

To read and write Stripe customer records from a shopper’s browser, the attackers hardcoded a Stripe secret key that begins with sk_test_, meaning the account runs in Stripe’s test mode. Stripe’s documentation on API keys separates publishable keys, which are meant for client-side code, from secret keys, which belong only on a server. Sansec draws the obvious conclusion. “No legitimate integration ever ships a Stripe secret key in client-side JavaScript,” the report says, calling such a key “by itself proof of compromise.”

What it takes from the checkout and how it leaves

The payload attaches itself to Magento’s checkout button, identified in the code by the .action.primary.checkout selector. When a shopper clicks it, the skimmer reads the card number, expiration date, and security code along with the billing address, email, phone number, and order total. It keeps the record only if every card field is filled in, encodes it with a simple XOR cipher, and saves it in the browser’s localStorage under the key cus_customer_id.

A second routine runs one second after each page load and then every 60 seconds. It splits the stored data in two, posts it to api.stripe.com/v1/customers as a new customer with a filler email address, and tucks the halves into metadata fields labeled customer_id and device_id. Once Stripe accepts the record, the routine deletes the local copy. In BleepingComputer’s summary, every stolen card becomes a fake customer in the attacker’s Stripe account.

Sansec also found a variant that swaps Stripe for Google Firestore. It pulls its payload from a document called tracking/captcha in a project named braintree-payment-app and stores data under the localStorage key _d_data_customer_. Sansec says both names were chosen to look like ordinary payment and bot-protection traffic.

Why a domain allowlist does not catch it

A Content Security Policy (CSP) is a set of browser rules that says which sources a page may load scripts from and which servers it may contact. Most stores that use GTM and Stripe allow both domains, because their own tags and payments need them. A skimmer that never leaves those domains looks, at the network level, like the store’s normal traffic.

The PCI Security Standards Council (PCI SSC) has said as much about CSP in general. Its information supplement on payment page security, announced in March 2025, lists tag management among the kinds of third-party scripts that “might be manipulated by malicious actors to capture payment data.” Among CSP’s disadvantages, it says CSP “by itself cannot create a list of unauthorized scripts” and is not able to confirm whether a malicious script changed its internal functionality “unless it also contacts disallowed domains.” This campaign is built to avoid exactly that condition.

Google’s own guide to using GTM with a CSP adds a wrinkle. It recommends putting a nonce, a random value generated for each page load, on the container snippet, and says Tag Manager “will then propagate the nonce to any scripts that it adds to the page.” In practice, a nonce on the container vouches for whatever tags the container holds, including a custom tag nobody at the store wrote.

Analysis: One CSP setting does work against this particular loader. Code built with new Function() is blocked by a CSP that does not include ‘unsafe-eval’ in its script-src directive. Google’s guide says GTM’s Custom JavaScript variables evaluate to undefined without ‘unsafe-eval’ and recommends Custom Templates instead, so stores that added ‘unsafe-eval’ to keep those variables working may want to revisit that choice on checkout pages. It is a narrow fix, since a different loader could avoid eval entirely.

What PCI DSS asks of checkout scripts

Two requirements in PCI DSS version 4.0.1 address this kind of attack and became mandatory on March 31, 2025. Requirement 6.4.3 says every script that runs on a payment page in the shopper’s browser must be authorized, checked for integrity, and listed in an inventory with a written reason for being there. Requirement 11.6.1 calls for a change and tamper detection mechanism that alerts staff to unauthorized changes to security-related HTTP headers and to script contents as the browser receives them, at least weekly or on a schedule set by a targeted risk analysis.

Merchants that validate with Self-Assessment Questionnaire A (SAQ A), typically those whose payment form is fully hosted by a provider, no longer answer 6.4.3 and 11.6.1 directly. The council’s January 2025 update to SAQ A removed them and added an eligibility condition that the merchant confirm its site “is not susceptible to attacks from scripts that could affect the merchant’s e-commerce system(s).” We explained that change in an earlier piece on SAQ A eligibility. A Magento store that collects card fields on its own checkout page, the kind this skimmer reads, still falls under both requirements through SAQ A-EP or SAQ D.

Checks drawn from Sansec’s findings

  • Search front-end JavaScript, including code delivered by GTM, for strings that begin with sk_test_ or sk_live_. Sansec says to treat any such key as a compromise.
  • Sansec gives the same advice for direct calls to api.stripe.com or firestore.googleapis.com in browser JavaScript, which in practice means comparing them against what the store’s own payment integration is supposed to do.
  • Open every GTM container that loads on the site. Sansec’s advice is to “review every tag you did not add yourself.”
  • Compare the site against the report’s indicators of compromise, which include the four container IDs, the Stripe customer ID, and the localStorage keys cus_customer_id and _d_data_customer_.

The report also recommends Sansec’s own Shield and eComscan products. Stores that use a different script monitoring tool can load the same indicators into it. For Magento teams, the GTM review fits alongside the retesting routine we described for payment integrations after security patches.

The short version for store owners

Nothing in this campaign required a new or suspicious domain. A tag inside a real GTM container fetched code from a Stripe customer record and sent Magento checkout data back to Stripe as fake customers, using a secret key that Stripe says should never appear in a browser. PCI SSC’s own guidance warns that CSP alone cannot spot a trusted script that has been changed, which is why 6.4.3 and 11.6.1 ask for an inventory and tamper alerts rather than an allowlist. The most practical checks are reviewing GTM tags, searching front-end code for Stripe secret keys, and looking for checkout pages that talk to payment APIs on their own.

Sources