A small hospitality business we’d just taken on as a client. They’d had a new website built earlier this year — modern, clean, well laid out. They were proud of it. We’d been brought in to look after the security side of things going forward.
The first thing we do with any new client is look at what’s actually live. Not what they think is there — what’s actually there. So we pulled up their homepage HTML.
It was the fourth line that stopped us.
Sitting directly inside the <head> tag — before the character encoding declaration, before any of the meta tags, before anything else — was a <script> tag pointing to a path on the client’s own domain. The path itself was nearly five hundred characters of random-looking text. Nobody had asked for it. Nobody knew it was there.
We had found a malware injection. In a brand new website. On a brand new client.
What We Found
With the actual token redacted, the tag looked like this:
<head><script type='text/javascript' src='https://<client-domain>/<471-character-opaque-token>'></script>
That placement is a fingerprint. Hand-authored HTML never puts a script tag directly after <head> without whitespace, and never before the character encoding declaration. That position is reserved — in attacker tooling — for prepended payloads, because it guarantees the script runs before anything else on the page can.
The script source is the more interesting half. Look at it: the host is the site’s own domain. The path is a 471-character base64-style token. To a browser, this is a first-party script — same origin as the page, same trust level. To most ad blockers and DNS-level filters, the same. To the average Content Security Policy if one was even present, this would have been allowed: script-src 'self' is what most people set, and “self” is exactly what an attacker exploiting a same-origin injection wants.
This is the most important thing to understand about this category of attack: it is invisible to almost every defence a small business has in place.
Why It Hadn’t Already Caused Damage
When we tried to load the URL the script tag pointed to, we got back the homepage HTML — not JavaScript. The site’s backend, by accident, returned the wrong content type for the malicious request. The browser tried to execute the homepage HTML as JavaScript, failed silently with a syntax error, and the attack never fired.
That accident was the only thing standing between the client and a live skimmer. It’s the kind of accident that gets undone without warning — by an attacker who fixes their tooling, or by a routine server change. Finding the injection now, before that flip, is why we look at every new client’s site first.
What It Could Have Done
When a script like this does fire — when the attacker’s endpoint is online and serving the real payload — here is what the campaigns we have seen in this family typically do.
- Form skimming
- The script attaches an invisible listener to every input on the page. The booking form on a hospitality site. The contact form on a solicitor's site. The login on an admin page. Each keystroke is captured and sent to the attacker's server. Within hours, customer names, phone numbers, email addresses, and any sensitive fields are sitting in a database that gets sold to other criminals. This category has a name in the industry — Magecart — and it has been used to steal payment data from very large brands. The same technique works perfectly on a five-page restaurant site.
- Admin credential theft
- Most small business websites have an admin login somewhere — a CMS, a booking system, a back office. The injected script runs on every page of the site, including the admin ones. When the owner logs in, the script reads the password straight out of the form field before the browser even sends it. Then it submits its own login attempt in the background. The attacker is now also logged in, with the same privileges, indefinitely.
- Session abuse
- Even where the password isn't easily readable, the script runs in the same security context as the legitimate session. It can make authenticated requests on behalf of the logged-in user. Download the customer database. Change the bank details on the payments page. Add a hidden admin account for later use. None of these actions trigger any warning, because they look identical to the owner's own activity.
- Redirect to scam
- The simplest payload, the most common, the easiest to monetise. The script checks where the visitor came from. If they arrived from Google or social media, redirect them to a fake "your computer is infected" scam page or a counterfeit payment portal. If they arrived directly or via a saved bookmark — which is what the site owner does — leave them alone. The owner never sees a problem. The visitors do, and the conversion losses are blamed on "the algorithm."
- Drive-by exploitation
- The high-value, lower-volume payload. Use the visit to fingerprint the visitor's browser and operating system, then attempt to deliver a real exploit — typically targeting an unpatched browser bug — to drop an information stealer onto the visitor's machine. We see this most often in industries where the visitor profile is valuable: legal, financial, healthcare. A skimmer on a small accountant's website is worth real money to a criminal group.
In none of these scenarios does the visitor see a warning. In none of them does an SSL certificate help.
Why HTTPS Doesn’t Save You From This
This is the question we get every time we explain this kind of finding to a business owner. “But we’re on HTTPS. Doesn’t that protect us?”
HTTPS protects data on the wire — the network path between the browser and your server. An eavesdropper on shared WiFi can’t read the traffic. A network attacker can’t tamper with the response in transit. The server’s identity is authenticated.
But by the time the malicious script runs, the network is already behind us. The browser has decrypted the HTML. The script tag is sitting in the page. The browser fetches the script — over HTTPS, perfectly encrypted — runs it as trusted code, and the attacker now has access to everything the page can see. When the script sends the stolen password to the attacker’s server, it sends it over HTTPS too. Beautifully encrypted, every step of the way to the criminal’s database.
The attack lives inside the browser’s trust boundary, not on the network. HTTPS is the wrong layer to defend at.
The flow looks like this — and every single arrow is HTTPS-encrypted:
Visitor's browser
│
│ ① GET / over HTTPS ✓ encrypted
▼
Your site
│ responds with HTML containing
│ <head><script src='self/<long-token>'>...
│
│ ② Browser fetches the injected script (HTTPS ✓)
│ Runs it inside YOUR origin (fully trusted)
│
│ ③ Script reads the admin password from the
│ form as the user types it
│
│ ④ Script POSTs the stolen password to the
│ attacker via fetch() (HTTPS ✓ encrypted)
▼
Attacker's server ── receives the credential
The padlock in the browser’s address bar was green the entire time.
How It Got There
This is the part the industry is still catching up to.
Our client’s website hadn’t been hand-coded by a developer. It had been generated — by an AI website builder, the modern kind that takes a description, a brief, or an existing site, and produces finished HTML ready to deploy. The exact tool isn’t the point; there are dozens of them now, and they are being used by small businesses every day to spin up new sites quickly and cheaply.
What we have found, repeatedly, is that some of these tools leak. They have been trained on, or scrape from, websites that were themselves compromised. The malicious script tags that lived in those compromised sites are sometimes reproduced in the AI’s output — with the source URL helpfully updated to point at the new site’s domain.
The result is a new website, freshly minted, that arrives at deployment already carrying a malware fingerprint. Sometimes the attack is dead — the attacker’s endpoint never existed, or the source host has been taken down. Sometimes it is very much alive. Either way, the script tag is now sitting in the HTML, in a public Git repository, ready to be pushed to production. The site owner doesn’t know. The deployment platform doesn’t flag it. The browser certainly will not.
This is a new face of supply chain risk. It is not a vulnerability in the AI tool, exactly — it is a vulnerability in trusting that what comes out of an automated process is clean simply because the process looks professional. We expect this category of finding to grow substantially over the next twelve months as AI-scaffolded sites become the default for new small businesses.
What We Did About It
The immediate fix was straightforward — remove the line, redeploy, verify the live site was clean. That took a few minutes.
The substantive work was everything else. We confirmed the rest of the codebase was clean. We checked the deployment pipeline. And we hardened the response headers so that even if a similar injection happened again it would not be able to execute.
That last point is worth dwelling on, because it is the part the rest of the industry is still figuring out.
A well-configured Content Security Policy — not the boilerplate one most sites have — can refuse to execute any script that isn’t on a pre-approved list, including scripts that come from your own domain. The mechanism is straightforward in principle and exacting in practice: cryptographic fingerprints of every legitimate script block on the page, baked into the response header at build time. If a script that doesn’t match a known fingerprint tries to run, the browser blocks it instantly and logs the attempt.
The attack we found would have been blocked under that policy. So would the next one. And the one after that.
Most small business sites do not have this. Many sites that do claim to have a CSP have configured it in a way that still allows the attack — the boilerplate “script-src 'self'” pattern is one of those, because as discussed above, “self” is exactly where the attacker is. The detail is what matters.
Why This Kind of Check Matters Before Launch
The traditional security audit happens once a year, by request, after something has gone wrong. The pace of website creation has changed; the cadence of checking it has not.
If you have built or commissioned a new website in the last twelve months — particularly one generated with AI tooling, a no-code platform, or a fast turnaround agency — you should not assume it is clean simply because it looks finished. The check takes minutes. The damage from not running it is open-ended.
Our free security check is a sensible starting point. It tests email authentication, security headers, exposed paths, and DNS posture — the publicly visible side of your security. What it does not do, on its own, is open up the HTML and look for the kind of script injection we have described here. That requires a manual review.
If you want that review — or if you have just stood up a new site and want it audited properly before it sits there quietly earning trust from your customers — get in touch. We will take a look and let you know what we find — no obligation.
The script tag that we found this month was, by accident, harmless. The next one will not be.