In August 2018 a customer landed on the British Airways payment page, typed in a card number, and got a boarding pass. Everything looked right. The page was served over HTTPS, the padlock was green, the money went to the airline's processor. What the customer could not see was twenty-two lines of JavaScript riding along with the page, copying every card number, expiry date, name and security code as it was typed, and shipping it all to baways.com - a look-alike domain registered by the attackers. British Airways put the first count at 380,000 transactions and later revised it to roughly 429,000 customers. The UK regulator opened at 183 million pounds and settled at 20 million.
The part I want you to sit with is where the theft happened. Not in the datacenter, not in the card vault, not in transit. It happened in the browser of every paying customer, executing code the customer's browser had been told to run by BA's own page. The attackers had slipped their lines into Modernizr, a small JavaScript library the payment page loaded, and the page handed the tampered file to visitors as part of normal business. Fifteen days passed before anyone noticed, and it was an outside researcher who noticed, not the airline - the regulator said as much when it catalogued BA's missing monitoring. The padlock in the address bar certified that data traveled encrypted. It said nothing at all about what the code did after it arrived.
The browser is on the other side of your wall
I take payments on a leaderboard where site owners bid real money for rank position, and for years I thought about security the way most owners do: as a wall. Firewall, patches, strong passwords, encrypted database - wall stuff. The BA incident is what broke that mental model for me, because the customer's browser is not behind any wall you own. It is a machine on the public internet that executes whatever your page tells it to execute, and your page tells it to execute code you did not write. Analytics. Chat widgets. Fonts. A/B testing tools. Ad pixels. A busy checkout page loads scripts from dozens of domains, and every one of them holds hands on your payment form.
Here is the opinion I will defend: a payment page carrying third-party scripts is a skimming risk you have accepted, and most owners accept it without ever seeing it as a decision. The decision was usually made by whoever installed a plugin in 2019 and has since left the company. Nobody audits the checkout page, because it works and nobody wants to touch it. BA had a security budget most companies cannot buy, and their skimmer still ran for fifteen days at a global airline before a stranger flagged it. Your detection will not be better. Yours will be a phone call from a customer's bank.

Move the card fields out of the blast radius
You cannot patch your way out of this one, because nothing of yours is broken. The fix is architectural, and the strongest version is blunt: your own page should never touch card data. Stripe's hosted card fields render inside an iframe from Stripe's own domain, and the same-origin rules that annoy developers the rest of the year become armor here - a script injected into your page cannot read keystrokes inside a frame it does not control. Your form receives a payment token instead of a card number, and your servers never see the real thing. That single choice deletes the payoff of a BA-style attack: a skimmer planted on your page harvests nothing, because there is nothing on your page to read.
The second half is boring and decisive: know exactly what your checkout loads. Every script on the payment page, on purpose, on a list, each with a reason attached. Subresource Integrity is one extra attribute on a script tag - the browser refuses to run the file if a single byte changes, so a tampered Modernizr dies in place. A Content Security Policy that names the origins scripts may load from turns an unknown domain into a hard error instead of a silent fetch. BA's skimmer exfiltrated to baways.com, a domain that looked almost right and worked completely. A strict policy would have starved it. None of this is exotic or expensive. All of it is skipped, everywhere, because the checkout already works and the quarterly review never gets that far down the agenda.
What it means here
Bid Spotlight charges real money for rank position, so I hold this blog to a rule: do not write about a security pattern the site does not already live by. Our card fields are Stripe's iframe, not our markup - a script planted on our page would sit outside the frame with nothing to steal. The payment page carries no analytics, no pixels, no widgets; it renders what the checkout needs and nothing else, deliberately dull. We store no card numbers because we never receive any. And no browser anywhere gets to say a transaction succeeded: that verdict arrives as a signed webhook from Stripe and is verified server-side before a single rank moves.

An owner who bids here is handing us a payment, and the honest way to repay that is a checkout where the trust does not depend on our perfection, or on the good behavior of every script a decade of maintenance has left lying on the page. The padlock in the address bar promises your data travels encrypted. Only architecture says what happens to it at the far end.
Open your own checkout page, view the source, and count how many domains your payment form is exposed to - then claim your spot at https://bidspotlight.com.
