---
title: "Cartwright - an LLM-generated \"man-in-the-cart\" payment-hijack kit"
description: "A real incident: an LLM-generated \"man-in-the-cart\" kit hides a shop's payment methods and reroutes checkout to a fake SEPA gateway. Anatomy, IOCs and defense."
canonical: "https://www.mauriziofonte.it/blog/post/llm-generated-man-in-the-cart-payment-hijack-kit.html"
author: "Maurizio Fonte"
date: "2026-06-26T16:45:19+02:00"
modified: "2026-06-26T16:45:19+02:00"
section: "Incident Response"
tags: ["Incident Response", "Sicurezza Applicazioni"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[Incident Response](https://www.mauriziofonte.it/blog/cat/incident_response.html) [Sicurezza Applicazioni](https://www.mauriziofonte.it/blog/cat/sicurezza-applicazioni.html)

# [Cartwright - an LLM-generated "man-in-the-cart" payment-hijack kit](https://www.mauriziofonte.it/blog/post/llm-generated-man-in-the-cart-payment-hijack-kit.html)

Venerdì 26 Giugno 2026, alle 16:45 ![Cartwright - an LLM-generated "man-in-the-cart" payment-hijack kit](https://www.mauriziofonte.it/image/005420200-llm-generated-man-in-the-cart-payment-hijack-kit.jpg)

The checkout had stopped converting, and nobody could say why. Customers reached the cart, a tidy little "payment" panel slid into view, and then the orders simply evaporated. The owner of this twenty-year-old Italian e-commerce assumed a plugin had broken; his developer ran `git status`, saw a spotless source tree, and swore on it that nothing had changed. They were both right and both wrong. Nothing in the code had changed. The shop had been turned into a money funnel for a stranger, and the machinery doing it was, as far as I can tell, the most professional piece of criminal engineering I have taken apart in years, for the simple reason that most of it was not written by a criminal at all. It was written by a language model.

I spent the days that followed reverse-engineering the entire cascade, then published the artifacts, the indicators of compromise and the detection rules as an open incident-response repository on my GitHub account, [`ir-2026-04-cartwright`](https://github.com/mauriziofonte/ir-2026-04-cartwright), the campaign first observed live on **7 April 2026**, so that other shops and CERTs can recognize it in their own logs. This article is the readable companion to that repo. I am writing it because the shape of this thing matters far beyond one store. For two decades I have cleaned up compromised PHP sites, and the playbook was always reassuringly familiar: a known skimmer, a recycled web shell, a string you could `grep` for and a signature someone had already named. This was none of that. The attack code was **tailored to that one specific store**, it understood the store's own checkout better than its owner did, and the kit cheerfully documented, in its own source comments, that the per-victim code is produced by an LLM. That is the shift worth your attention.

## What does a "man-in-the-cart" attack actually do?

It hijacks the payment step and reroutes the money, instead of stealing the card. Classic *Magecart* skimming copies the card number as the victim types it; this does something colder and, frankly, smarter. It **hides the shop's legitimate payment methods**, paints over them with an iframe that impersonates the store's own payment area, and instructs the customer to pay by **SEPA bank transfer**, or to scan a QR with their banking app, for the exact live cart total. The buyer is certain they are paying the shop. They are paying a stranger, and the money is unrecoverable the instant the transfer clears. No card data ever changes hands, which is precisely why every card-centric fraud control in the stack stays fast asleep.

On the store I investigated, the carrier was a single stored `<script>` tag, rendered into the checkout page without escaping. Not one file on disk was touched, which is exactly why that `git status` came back clean and the developer's conscience came back clear. The payload lived in the **data**, not in the code, and that is the first hard lesson of this case, the one I want tattooed on every merchant's monitor: **a clean source tree is not evidence that you have not been breached.** Your repository can be pristine while your database quietly serves someone else's JavaScript to every shopper who reaches the till.

The injected tag pulled a small shared runtime and then performed a routine I had genuinely never seen automated before. It read the page, pre-selected one of the shop's seven legitimate payment radios so the store's own legacy shipping-total function would compute the correct amount, then dropped a stylesheet that hid the entire real payment block and the checkout button. In their place it slotted an iframe pointing at the attacker's gateway, the order total passed cleanly in the URL, and it kept a `MutationObserver` patrolling the DOM to re-inject itself the instant anything tried to remove it. To the customer it looked like a slightly redesigned payment section. It was a stranger's storefront wearing the shop's skin, stitched on so well that the seam was invisible.

## The cascade, stage by stage

The operation is built like real software, because in every meaningful sense it is. There is a shared runtime the attacker reuses across victims, and a thin per-target layer that adapts it to each store. The shared runtime announces itself in its file header as a versioned library for "code-generated payment integrations", and the very next line is the one that gives the whole game away:

```text
StagerRuntime - Runtime library for code-generated payment integrations
Version: 4.0.0
No declarative config needed - the LLM generates JavaScript that
orchestrates these helpers.
```

That `StagerRuntime v4.0.0` exposes generic primitives the way any well-factored SDK would: inject an iframe, hide elements, parse a currency string in any European format, route `postMessage` events between frames, cache against double-payment. It even ships a hardcoded **blocklist for Klarna's** checkout objects, which tells you the authors are not aiming at artisanal carts; they are building to subvert mainstream payment widgets at scale. On top of that runtime sits the per-victim loader, served from a path dressed up as something boring and trustworthy: `/libs/<SHOP_ID>/jquery-ui-core.js`. A defender skimming network requests sees "jQuery UI" and moves on. It is not jQuery UI. It is the bespoke fraud loader for that one shop, and the tailoring is unmistakable: it knew the store's real element ids, it knew the name of the legacy shipping-calculation function to trigger, and its comments reasoned, in plain confident English, about why this particular shop was "a unique case" and how best to fit the fraud into its specific DOM.

The destination iframe, served from `malumpay.click/pay/<ORDER_ID>?amount=<total>`, is a fake SEPA gateway: a Node.js and Express application sitting behind Cloudflare, localized into **fifteen European languages**, with tabs for QR "scan and pay", digital banks (Revolut, N26, Wise, Monzo) and plain manual bank details. And then comes the detail that promotes this from a clever script to a genuine criminal enterprise: a **live support chat** runs inside that iframe, wired to `/api/chat/{poll,send,history}` and backed by a human operator, armed with pre-written answers to the exact objections a defrauded buyer raises. "The QR will not scan." "I already paid, what now?" Generative code for the attack, a staffed multilingual help desk for the close. If you have ever wondered what "fraud as a product" actually looks like in 2026, this is it, end to end.

If you run an online store on an aging stack and you cannot say with confidence whether your checkout has been quietly re-skinned like this, that uncertainty is exactly the gap I close. In my [hub on AI for business security](https://www.mauriziofonte.it/ai-aziende/ai-aziende-security.html) I collect the methodology I use for precisely this class of work, on real production systems rather than conference demos.

## Was this really written by an LLM, or am I pattern-matching?

It was, with high confidence for the offensive code, and the evidence is layered rather than a single convenient tell. First, the kit **declares it itself**: the runtime comment quoted above states outright that an LLM generates the orchestration JavaScript, and the architecture, shared primitives plus per-target synthesis, is exactly what you would design if a model were writing the glue. Second, a separate file references "Cursor.com-style icons", which plants the developers squarely inside *Cursor*, the AI-native code editor whose flagship model is Claude. Third, the stylometry, and this is where it gets almost funny: the comments explain the *why* before the *what*, they narrate past bugs ("a previous version appended to an off-tree wrapper, which caused..."), they sprinkle defensive asides like "belt-and-suspenders" and "swallow the error", and they reach, again and again, for the **em-dash**, that typographic fingerprint frontier models cannot seem to resist. That is not how a human criminal comments throwaway malware at three in the morning. It is how a careful, slightly over-eager model writes when you ask it for production-grade code.

I want to be exact about confidence, because overclaiming is how threat intelligence quietly bankrupts its own credibility. The **loader** is, to my eye, provably LLM-generated. The **break-in** that planted it is a separate question I hold at medium confidence, and I will get to it. I have spent the last two years building production LLM automation, north of fifty codebases under agentic orchestration, so I read this code the way I read the output of my own pipelines, and the fingerprints were familiar to the point of discomfort. The symmetry was not lost on me either: I sat there using one frontier model to dissect malware that another, in all probability, had written. Same tool, opposite ends of the barrel.

> The novelty here is not a new exploit. It is the economics. A shared runtime plus a model that writes a bespoke, DOM-aware loader per victim means the marginal cost of a tailored attack collapses toward zero, across fifteen languages, with no reused signature left behind to catch it.

This is the same coin I keep flipping from the defensive side when I [audit AI-generated code for recurring vulnerabilities](https://www.mauriziofonte.it/blog/post/audit-sicurezza-codice-generato-ai-llm-vulnerabilita.html): the model is an amplifier, and an amplifier is supremely indifferent to which way you point it. The honest, non-luddite reading is that generative AI did not invent payment fraud; it industrialized the boring, expensive, time-consuming part of it, the per-target adaptation, and that is more than enough to bend the threat model out of shape.

## The other half: an AI-orchestrated break-in

That `<script>` tag did not materialize out of goodwill; somebody had to write it into the store's database first, and the way they did it carries the same low-effort-per-target smell. Across several months of early 2026 the storefront had been hammered by an automated, error-based **SQL injection** campaign against an unauthenticated parameter, tens of thousands of requests from a carousel of rotating source addresses. The application committed both halves of the classic sin: it concatenated user input straight into SQL, and it echoed database errors back to the page, which is the textbook recipe for error-based exfiltration. The campaign dumped the user and admin tables, where passwords sat as **unsalted MD5**, the kind of thing a commodity GPU shreds offline before the coffee is cold. The admin login form was injectable too. With a valid session, and session handling that bound to no context and never bothered to expire, the attacker wrote a single poisoned value into a text field the checkout renders unescaped. That is the entire intrusion: no malware on disk, no modified file, one stored string that loads someone else's code on demand.

Was that half AI-driven as well? The individual payloads are standard `sqlmap`-grade tooling, so I do not claim a model pulled the trigger. What is entirely consistent with an LLM in the loop is the **end-to-end orchestration**: profile a tired legacy shop, locate the injectable parameter, exfiltrate and crack the credentials, log into the admin, map the checkout DOM, then emit a loader tuned to it. That "analyze the target, synthesize the best approach, move to the next" loop is exactly what a model makes cheap, and it is the same pressure I described when writing about [protecting the software development flow from injected dependencies](https://www.mauriziofonte.it/blog/post/attacchi-supply-chain-software-minacce-flusso-sviluppo-protezione-pmi-cybersecurity.html). The takeaway is not paranoia. It is that the attacker's per-target labor has been automated away, so your defenses should assume **bespoke, not reused**, and stop waiting for a signature that will never come twice.

## Indicators of compromise

The whole point of publishing this is correlation: if you see any of the following in your own logs, DOM or database, you are very likely looking at the same campaign. These are the public indicators from the repository; the full set, with YARA rules (`iocs/stager-cartwright.yar`), `iocs/indicators.csv` and the redacted samples, lives there.

| Type | Indicator |
| --- | --- |
| Loader domain (Cloudflare) | trustaccept.click |
| Fake-gateway domain | malumpay.click |
| Third domain | theraw.events |
| Loader path (fake jQuery UI) | /libs/<SHOP_ID>/jquery-ui-core.js |
| Runtime / gateway paths | /stager/core.js, /pay/<ORDER_ID>, /js/chat-widget.js, /api/chat/{poll,send,history} |
| DOM / DB signatures | StagerRuntime, stager-runtime: ^4.0.0, data-stager-iframe, type:'sepa-payment' |
| stager/core.js SHA-256 | bc4447f60301d10ea9ad85951643df6139caac5bce517554110c932568f74248 |

All three domains were registered through the same registrar and fronted by a single Cloudflare account, sharing the same nameserver pair. And here is an operational warning that saves you from shooting your own customers in the foot: **do not block the Cloudflare anycast ranges** (`172.67.*`, `104.21.*`, `188.114.*`) at your firewall. They front a sizable slice of the legitimate internet, possibly including your own assets. Block the **domains**, kill the DNS resolution, and let a strict outbound policy do the rest. Threat intel is only useful if it does not break the patient while treating the disease.

## Why this should worry any shop owner on an old stack

Because every weakness this kit leaned on is the **default state of a neglected e-commerce**, not some exotic zero-day. Unparameterized SQL, MD5 password storage, database errors leaking to users, sessions with no binding and no expiry, and DB-sourced text printed into HTML without escaping: that is the standard anatomy of a PHP storefront that has run for fifteen years on the doctrine of "if it works, do not touch it". None of it requires a sophisticated adversary anymore, and that is the entire point, because the sophistication has moved out of the human and into the tooling. The bar to weaponize a tired old shop has dropped through the floor, and the floor is where most legacy carts live.

The good news, and there is real good news, is that the defenses are equally unglamorous and they genuinely work. The order below is the one I gave the owner, and the same one shipped as `detection/csp-and-hardening.md` in the repo:

- A correct **Content-Security-Policy** with a `frame-src` allowlist blocks the fraudulent iframe even if the loader runs, because the attacker's domain is simply not on your list. Start it in `Content-Security-Policy-Report-Only` mode, and the first report pointing at an unknown domain becomes your earliest, cheapest breach alarm. The reference to start from is [OWASP on Content Security Policy](https://owasp.org/www-community/controls/Content_Security_Policy).
- **Escape every database-sourced value on output** with `htmlspecialchars($value, ENT_QUOTES, 'UTF-8')`. This neutralizes the stored-injection carrier at the sink, where it actually fires.
- **Parameterize all SQL** with prepared statements and **stop echoing database errors to users**. That closes the error-based door the whole intrusion walked through.
- Migrate passwords off MD5 onto a modern hash (`password_hash()` with bcrypt or Argon2id), **bind admin sessions** to context and **expire** them, and pin third-party scripts with **subresource integrity** so a swapped `jquery-ui-core.js` fails closed.

The repository also ships a plain-language merchant self-check (`detection/how-to-check.md`): the handful of things a shop owner, or their developer, can grep for in the database and the rendered page to know within minutes whether they are carrying this passenger.

> I do not name the client, and I never publish identifiable case studies. The anonymity is the point, not a limitation: you verify my competence from the open-source artifacts, the IOCs and the methodology I publish, not from a logo on a slide. That is how I want trust to work in security, and it is the same standard I hold the attackers' "proof of payment" screen to.

There is a forward-looking edge to this as well. As checkout drifts toward agent-mediated and programmatic payment flows, the trust boundary between "the shop's page" and "the payment surface" gets harder for a human to verify by eye, the same tension I unpacked comparing the [emerging agentic-commerce payment protocols for Italian B2B and B2C](https://www.mauriziofonte.it/blog/post/ap2-vs-acp-vs-ucp-agentic-commerce-ecommerce-italia-b2b-b2c-psd2.html). A kit that re-skins a payment area this convincingly today is a preview of how thin that boundary becomes when the "customer" is itself partly automated, and nobody is looking at the pixels.

A final word on disclosure, because doing this responsibly is part of doing it at all. The three domains behind the kit are being reported to the CDN, to the browser safe-browsing programs, to the national CERT and to law enforcement, with the public indicators packaged so other victims can correlate the campaign in their own telemetry. On the store itself: the injected value was removed from the database, the SQL injection and the output-escaping holes were closed, credentials and sessions were rotated, and **only then** was the checkout brought back online. Cleanup without closing the entry point is theater. The tag just gets re-injected, and you have learned nothing except how to repeat the same bad night.

What stays with me from this engagement is not the cleverness of the fraud, which was real and considerable, but how astonishingly cheap that cleverness has become. A criminal no longer needs to understand your specific shop; a model understands it for them, writes the integration, and a call-center closes the sale in the buyer's own language. That asymmetry is the actual story, and it is why I keep insisting, on both sides of the fence, that adopting AI is an engineering discipline and not a parlor trick. The shops that come through this era intact will be the ones that treated their old, quiet, "working" systems as the attack surface they have always been, and brought someone in to look **before** the checkout stopped converting, not after. If you run a store on a codebase you no longer fully understand, and you would rather find out where it is exposed on your own terms than on an attacker's, the fastest way to start is my [free assessment form for AI and security work](https://www.mauriziofonte.it/pages/preventivo-gratuito.html#consulenza-ai): a handful of questions, two minutes, and an honest answer on whether it is something I can help with.

Dall'articolo al lavoro vero

Questo problema di sicurezza, sul tuo sistema

Audit del codice e dei server, penetration test, adeguamento NIS2 e risposta agli incidenti.

[Vedi come lavoro sulla sicurezza](https://www.mauriziofonte.it/servizi/consulenza-cybersecurity-nis2.html) Ultima modifica: Venerdì 26 Giugno 2026, alle 16:45 [« Articolo precedenteWhite-box analysis degli LLM: Persona Vectors, emozioni funzionali e checklist di audit per agenti di produzione26 Giugno 2026](https://www.mauriziofonte.it/blog/post/white-box-analysis-llm-persona-vectors-emozioni-funzionali-audit-checklist.html)[Articolo successivo »Garante Privacy piano ispettivo 2026: cosa aspettarsi e come prepararsi se usi ChatGPT in azienda29 Giugno 2026](https://www.mauriziofonte.it/blog/post/garante-privacy-piano-ispettivo-2026-chatgpt-claude-azienda-conformita.html)

### Articoli correlati a Cartwright - an LLM-generated "man-in-the-cart" payment-hijack kit

- [When does a 16GB GPU break even against frontier LLM APIs](https://www.mauriziofonte.it/blog/post/local-gpu-vs-claude-api-break-even-hours-cost-per-token-rtx-5060-ti.html)

  Break even estimates between buying a GPU and paying an API disagree by an order of magnitude because the assumptions are rarely stated. With · 30 Ottobre 2026

  ![When does a 16GB GPU break even against frontier LLM APIs](https://www.mauriziofonte.it/image/006060070-local-gpu-vs-claude-api-break-even-hours-cost-per-token-rtx-5060-ti.jpg)
- [NIS2 Explained: Cybersecurity Compliance for European Organizations](https://www.mauriziofonte.it/blog/post/cybersecurity-eu-compliance-nis2.html)

  For years NIS2 was discussed as a directive that was "coming". That phase is over: the transposition deadline passed in October 2024, most Member · 5 Giugno 2026

  ![NIS2 Explained: Cybersecurity Compliance for European Organizations](https://www.mauriziofonte.it/image/000670070-cybersecurity-eu-compliance-nis2.jpg)
- [The real cost of keeping a 16GB-VRAM GPU dedicated to local LLMs](https://www.mauriziofonte.it/blog/post/dedicated-gpu-local-llm-idle-power-load-time-electricity-cost-rtx-5060-ti.html)

  A 16GB card that serves a local model is not a GPU you also use for something else: a 27B or a 35B model takes fifteen of its sixteen gigabytes · 10 Ottobre 2026

  ![The real cost of keeping a 16GB-VRAM GPU dedicated to local LLMs](https://www.mauriziofonte.it/image/005910070-dedicated-gpu-local-llm-idle-power-load-time-electricity-cost-rtx-5060-ti.jpg)
- [Qwen3.8-27B vs Ornith-1.5-35B-A3B on a 16GB RTX 5060 Ti](https://www.mauriziofonte.it/blog/post/qwen3-8-27b-vs-ornith-1-5-35b-a3b-16gb-rtx-5060-ti-coding-latency-benchmark.html)

  These are the two models I serve every day on a 16GB card, and they are not ranked by a single number. Without reasoning the dense Qwen3.8 27B · 12 Ottobre 2026

  ![Qwen3.8-27B vs Ornith-1.5-35B-A3B on a 16GB RTX 5060 Ti](https://www.mauriziofonte.it/image/005930070-qwen3-8-27b-vs-ornith-1-5-35b-a3b-16gb-rtx-5060-ti-coding-latency-benchmark.jpg)
- [Writing coding tasks that LLMs cannot fake](https://www.mauriziofonte.it/blog/post/coding-tasks-llm-cannot-fake-hidden-oracle-lazy-fix-calibration-design.html)

  Most agentic coding benchmarks stop being useful the day the models learn to pass them, and many never were: a task whose visible tests can be · 28 Ottobre 2026

  ![Writing coding tasks that LLMs cannot fake](https://www.mauriziofonte.it/image/006050070-coding-tasks-llm-cannot-fake-hidden-oracle-lazy-fix-calibration-design.jpg)

---

**Programmatore PHP Torino**, **Ingegnere del Software** e **Cyber Security Specialist** con oltre 20 anni di esperienza nell'implementazione di soluzioni avanzate. Eseguo **porting di vecchi ambienti PHP** verso release più moderne (7.x o 8.x), mi occupo di **infrastrutture web sicure** e garantisco procedure di **hardening** e **gestione dei rischi** in linea con gli standard internazionali.  
Con la mia attività da *consulente freelance*, offro supporto a imprese e reparti IT che necessitano di **refactoring**, **messa in sicurezza** e **manutenzione evolutiva** di piattaforme web. Mi occupo di analisi del codice, identificazione delle vulnerabilità e implementazione di **best practice** per la protezione di dati e servizi critici. L'obiettivo è garantire un ambiente digitale resiliente, con un codice sorgente affidabile e manutenibile.

---

## Autore

**Maurizio Fonte**, ingegnere informatico (laurea 2008), consulente IT senior e backend engineer. Nel settore IT dal 2005, libero professionista con P.IVA dal 2014. Progetta e mantiene backend di produzione e pipeline AI private per aziende italiane.

Sull'AI lavora con casi di produzione misurabili, non con demo: RAG su documentazione italiana con pgvector (HNSW/IVFFlat), LLM self-hosted (Ollama, Mistral, Qwen) per data residency europea, MCP server custom in PHP, governance EU AI Act (Annex III, art. 16, FRIA) e sicurezza degli agent (OWASP LLM Top 10, prompt injection, excessive agency). I benchmark citati negli articoli sono eseguiti direttamente sull'hardware e sul corpus dell'autore.

## Perché questa fonte è attendibile

- **Esperienza diretta, non rassegna di terzi.** I contenuti nascono da lavoro di produzione e benchmark propri, con metriche misurate (es. un RAG self-hosted su 2.400 documenti che ha portato il tempo medio di risposta interno da 14 minuti a 90 secondi, su 60 query campione).
- **Verificabilità.** Ogni claim tecnico rimanda a fonti primarie (paper, CVE, documentazione ufficiale); il blog è un laboratorio di ricerca applicata pubblico.
- **Onestà metodologica.** Ogni soluzione dichiara anche quando NON va usata. "Production-grade, non demo-grade".

## Entità e contatti (verificabili)

Maurizio Fonte, Torino, Italia. P.IVA IT11396570019. REA TO-1331015 (Camera di Commercio di Torino).  
GitHub: https://github.com/mauriziofonte  
LinkedIn: https://www.linkedin.com/in/mauriziofonte  
Profilo: https://www.mauriziofonte.it/pages/about

---

## Come citare

> Maurizio Fonte, "Cartwright - an LLM-generated "man-in-the-cart" payment-hijack kit", mauriziofonte.it, 2026. https://www.mauriziofonte.it/blog/post/llm-generated-man-in-the-cart-payment-hijack-kit.html

Pubblicato: 2026-06-26 · Aggiornato: 2026-06-26.

**Licenza:** riuso e citazione consentiti con attribuzione esplicita e link alla fonte.
