Back to Article List

Catch JavaScript Skimmers Hiding on a 'Healthy' Store

Detectează skimmere JavaScript ascunse în magazinul tău - Catch JavaScript Skimmers Hiding on a 'Healthy' Store

If your malware scanner says your store is clean but customers still report fraud, the thief is probably not on your server at all. Card skimmers now live in the browser, injected through a third-party script or a compromised dependency, running only for the shopper on the checkout page. Your server never sees the stolen data, so a server-side scan never flags it.

Cloudflare recently disclosed four separate client-side campaigns that did exactly this: they stayed invisible to server scans while quietly reading card fields and clicks. The fix is not another scanner. It is a short list of browser-level controls you can set up yourself in an afternoon, and a habit of watching what your checkout page actually loads.

Here is the detection routine, in the order I'd do it.

Why server scans miss browser skimmers entirely

A JavaScript skimmer runs in the shopper's browser, so anything that only inspects files on your server is looking in the wrong place. The malicious code often isn't even stored on your host. It's pulled in at runtime from a third-party domain — an analytics tag, a chat widget, a payment helper, a font loader — or it's slipped into a legitimate library you trust. The attacker changes the remote file; your files never move.

That's why your host's overnight scan comes back green. The skimmer only activates under specific conditions: on the checkout URL, for a real visitor with a mouse and a session, never for a headless scanner hitting the page from a data centre. Some campaigns Cloudflare found only fired after a shopper started typing into a card field.

The practical takeaway: you need to know exactly which scripts your checkout runs, where each one comes from, and get told the moment that list changes. Nothing on the server tells you this.

Step one: take inventory of every script your checkout loads

Open your checkout page, open DevTools, and write down every external script — because you cannot protect a list you don't have. In Chrome or Firefox, go to the Network tab, filter by JS, and reload the page. Then do the interactive part: click into the card number field, type a fake number, and watch for scripts that load only after you interact. Those late loaders are the ones worth the most attention.

Sort what you find into three buckets:

  • Yours — scripts served from your own domain.
  • Known third parties — payment gateway, analytics, cookie banner, live chat. Each one is a company you chose.
  • I have no idea — anything you can't immediately name. This bucket is your priority. A skimmer usually hides here, often on a domain that looks almost right (think g-analytics.com instead of google-analytics.com).

Do the same in the Console: run document.scripts to list every script element, and check the Application > Storage tab for anything writing card-shaped data to localStorage or firing requests to unfamiliar domains. Skimmers frequently stash captured data locally before exfiltrating it in a single beacon.

Step two: lock the page down with a Content Security Policy

A Content Security Policy tells the browser which domains are allowed to run scripts, so an injected script from an unknown domain simply won't execute. This is the single most effective control against client-side skimmers, and it's a response header you can add today.

Start in report-only mode so you don't break your own store. This watches and logs without blocking:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://js.stripe.com https://www.googletagmanager.com; report-uri /csp-report;

Load your site for a few days, read the reports, and add the legitimate domains you missed. Once it's quiet, flip the header name to Content-Security-Policy to start enforcing. From that point, a script from a domain not on your allow-list is dead on arrival — it never gets to read a card field.

Two rules that matter more than the rest: avoid 'unsafe-inline' in script-src if you possibly can, because it hands attackers the easiest injection path, and keep the allow-list as short as your store can tolerate. Every domain you allow is a domain that can be compromised on your behalf. If you're on a TPC Hosting plan, you can add this header in your .htaccess or your app config — and if you're not sure it's landing correctly, our chat is on any hour of the day and a real engineer follows up during business hours.

Step three: pin third-party files with Subresource Integrity

Subresource Integrity (SRI) makes the browser refuse to run a third-party file if its contents have changed even by one byte. This closes the exact gap the Cloudflare campaigns used: a library you trust getting swapped out at the source. You add a cryptographic hash of the file you expect, and if the served file doesn't match, the browser blocks it.

You add two attributes to the script tag:

Generate the hash with a quick command:

cat lib.js | openssl dgst -sha384 -binary | openssl base64 -A

Or grab it from srihash.org by pasting the URL. The one honest trade-off: SRI works for files that don't change, so it fits fonts, CSS frameworks and pinned library versions. It doesn't fit a script that legitimately updates itself, like some tag managers or a payment SDK the provider revs often — for those, lean on your CSP allow-list instead. Use each tool where it's strongest.

Step four: watch for changes so you find out in hours, not weeks

Set up a simple check that alerts you when your checkout's script list changes, because most skimmer victims only learn about it from their bank. You don't need a paid platform for this. A CSP report-uri already gives you a live feed of blocked or unexpected scripts — point it at a small endpoint that emails you when anything arrives.

For a lightweight belt-and-braces approach, run a scheduled job that fetches your checkout page and diffs the list of script src URLs against a saved baseline. A few lines of Python with requests and BeautifulSoup on a daily cron is enough. When the diff isn't empty, you get an email, and you investigate before your customers do.

Here's the routine as a checklist you can keep:

  • Weekly: reload checkout in DevTools, confirm the script list matches your baseline.
  • On every third-party addition: add its domain to the CSP allow-list, nothing more.
  • On every pinned library: generate a fresh SRI hash and update the tag.
  • Always on: CSP in enforce mode with a live report-uri.
  • After any plugin or theme update: re-check the script list — updates are a common re-injection point.

All of this runs on ordinary hosting; you don't need special infrastructure. TPC Hosting keeps your store on EU servers under GDPR rules, which matters here because a skimmer breach is also a personal-data breach — the shorter your window between infection and detection, the smaller that problem gets.

What to do the moment you find one

If you spot a rogue script, remove or block it first and investigate second, because every minute it runs is more stolen cards. Pull the offending domain from your allow-list so the CSP kills it immediately, then find how it got in: a compromised plugin, a hijacked third-party account, or a stolen admin login.

Then rotate credentials — CMS admin, hosting, and any third-party tag manager account — and force-update every plugin and dependency. Check your payment processor's dashboard for suspicious activity, and if card data was exposed, you have notification duties under GDPR and your card scheme rules. Don't sit on it. If you're a TPC customer and you're unsure whether an injection touched anything on the server side, reach out on chat and we'll help you check the logs and lock things down.

FAQ

Can a malware scan really miss a card skimmer?

Yes — most scanners inspect files on your server, but a JavaScript skimmer runs in the shopper's browser and often loads from a third-party domain your files never reference. The skimmer also tends to activate only on the checkout page for a real visitor, so an automated scan hitting the page from a data centre never triggers it.

Will a Content Security Policy break my store?

Not if you roll it out in report-only mode first. Use the Content-Security-Policy-Report-Only header for a few days to see exactly which domains your store needs, add the legitimate ones to your allow-list, then switch to enforcing. Done this way, only unexpected scripts get blocked.

When should I use SRI instead of CSP?

Use Subresource Integrity for third-party files that don't change, like fonts, CSS frameworks and pinned library versions. Use your CSP allow-list for scripts that legitimately update themselves, such as tag managers or payment SDKs, since a changing file would fail an SRI hash check every time.

How often should I check my checkout for rogue scripts?

Check weekly in DevTools and immediately after any plugin, theme or dependency update, since updates are a common re-injection point. Pair that with a live CSP report-uri so you're alerted automatically whenever an unexpected script appears between checks.

Is a skimmer infection a GDPR issue?

Yes — a skimmer that captures customer card and personal data is a personal-data breach under GDPR. That triggers notification duties, so the faster you detect and stop it, the smaller your legal and reputational exposure. Hosting your store on EU servers keeps the data-handling side simpler.