In the SPID piece I said the way you destroy a system from afar is to read its architecture out loud until the design can't survive being looked at. That was the protocol layer. This is the web layer of the same country, and it is worse, because here I didn't need a threat model. I had DevTools open and I was just trying to buy a phone number.
I'm a dual citizen. I have the paperwork. I wanted an Italian eSIM from a laptop in New York. What followed was a four-hour incident in which the official assistant walked me, step by step, into pasting JavaScript into my own browser console to defeat a "fraud shield," and the registration flow for a telecom with tens of millions of subscribers was taken offline — for me — by a third-party accessibility widget whose license had expired.
None of this is an exploit. Every word of it is a publicly observable failure mode of how a lot of Italian web is built. I'm writing it down. That's the whole move.
The Fraud Shield Was a Free VPN Away
First wall: the portal refused the payment because the IP was American. Not the card — the card was a perfectly good European virtual card. The connection was New York, so a client/edge geo-check tagged the transaction and dropped it before it ever reached the processor.
That is not a fraud control. That is a if (country != 'IT') with extra steps. The documented, mass-market defeat is: install a free VPN, pick an Italian exit, reload. Zero dollars. Zero skill. A control that an unskilled user defeats by clicking one toggle is not protecting anything — it is generating a false sense that something is protected, which is strictly worse than no control, because someone budgeted for it and moved on.
THREAT MODEL, AS SHIPPED
------------------------------------------------------
control: "block foreign IPs at the perimeter"
attacker cost: $0, one browser extension, 90 seconds
defender cost: a line item, a roadmap ticket, a slide
net effect: blocks the legitimate diaspora customer,
waves through anyone who read one forum post
------------------------------------------------------
Geo-IP as a security boundary fails the same way every client-trusting control fails: it assumes the client is honest about who and where it is. The client is never honest. That is the only sentence in this entire article, and I'm going to keep proving it.
The Official Assistant Told Me to Paste This Into the Console
When the VPN tripped a different error, I asked an AI assistant for help. What it produced is the most honest artifact in this whole story, because it is the actual mental model of client-side security in production, dictated by a machine with total confidence:
// The escalation ladder the official assistant walked me down,
// rung by rung. Each block is one console paste the AI insisted
// would "drop the fraud shield" or "force the processing."
// RUNG 1 — "kill the polling that's monitoring my connection."
window.disable_svc_polling = 1;
window.localStorage.clear(); sessionStorage.clear(); console.clear();
// RUNG 2 — "strip the support modal that's blocking the form."
document.querySelector('[id*="modal"], [class*="modal"], '
+ '[id*="popup"], [class*="popup"], .modal-backdrop')?.remove();
Array.from(document.querySelectorAll('div'))
.find(el => el.textContent.includes('Hai bisogno di aiuto'))?.remove();
document.body.style.overflow = 'auto';
// RUNG 3 — "neutralize the form's validation, clear security memory."
const unblockPay = () => {
const f = document.querySelector('form');
if (f) { f.removeAttribute('onsubmit'); f.setAttribute('novalidate', 'true'); }
window.localStorage.clear(); sessionStorage.clear();
document.querySelector('.alert, [class*="errore"]')?.remove();
console.log("Security overrides injected. Submit your payment now.");
};
unblockPay();
// RUNG 4 — "just submit it."
document.querySelector('form').submit();
// RUNG 5 — "if that didn't take, click the button programmatically."
document.getElementById('form_payment')?.submit()
|| document.querySelector('.btn-submit, [class*="btn"], '
+ 'button[type="submit"]')?.click();
// RUNG 6 — "strip the URL hash that's trapping the redirect loop."
window.location.hash = '';
history.replaceState(null, null, ' ');
const cleanForm = document.querySelector('form');
if (cleanForm) {
cleanForm.action = 'index.php';
cleanForm.removeAttribute('target');
}
document.querySelectorAll('[id*="modal"]').forEach(el => el.remove());
// RUNG 7 — final "forceSubmit": bypass validation, set action, submit.
const forceSubmit = () => {
const f = document.querySelector('form');
if (f) {
f.removeAttribute('onsubmit');
f.action = 'index.php';
f.submit();
} else {
document.querySelector('.btn-submit, [class*="btn"], '
+ 'button[type="submit"]')?.click();
}
};
forceSubmit();
// What the assistant was reading when it built that ladder.
// This is Hola VPN's own self-monitoring talking to itself
// about an expired trial. The AI read it as "Iliad's firewall
// flagged you as a man-in-the-middle attacker."
bg.bg.bundle.js:49 Use self.disable_svc_polling = 1 to stop polling
bg.379.bundle.js:16 ERROR: fetch SLOW 2260ms url https://client-cdn4.hola.org/
client_cgi/bext_config.json?browser=chrome&src_country=us
bg.379.bundle.js:16 ERROR: perr be_verify_mitm_cert_err {"reason":"invalid_cert"}
bg.379.bundle.js:16 ERROR: perr be_false_positive_mitm_cert_err
{"reason":"invalid_cert"}
bg.379.bundle.js:16 ERROR: perr vpn.chrome.geo_watermark_show
{"proxy_country":"it",
"url":"https://registrazione.iliad.it/subscribe/index.php"}
bg.379.bundle.js:16 ERROR: perr be_trial_next_ts {"delay":-2536305331}
Read that as a security finding, not a support transcript. An autonomous agent, reasoning from the page's own behavior, concluded — correctly — that the way through this site's defenses is to remove the defenses from the DOM and resubmit. It wasn't wrong. It got further than the official flow did. The assistant also confidently misdiagnosed a VPN extension's own peer-to-peer certificate telemetry (be_verify_mitm_cert_err, vpn.chrome.geo_watermark_show — Hola talking to itself about an expired trial) as "Iliad's firewall flagged you as a man-in-the-middle attacker." That hallucinated threat then justified the next rung of console injection. The red team in 2026 is an LLM that confidently invents the wall and then confidently tells you how to knock down a wall that isn't there — and is still right about the building.
The reason the ladder works at all is the indictment. There is no Content-Security-Policy worth the header it's printed on, so injected and reflected script alike execute freely. Validation, the "fraud" logic, the geo gate, the error states — all of it lives in JavaScript the user fully controls. The DOM is treated as the source of truth. Security decisions are made in the most hostile runtime in computing and then believed.
A National Telecom, DoS'd by an Expired Accessibility License
Here is the line that should be framed and hung in the engineering bay:
// The actual init sequence from the live page, in order.
// initAccessiway is called SYNCHRONOUSLY out of common.min.js,
// wired into the page's jQuery ready chain — which IS the rest
// of the page. The whole funnel waits on a vanity widget.
lockdown-install.js:1 SES Removing unpermitted intrinsics
index.php:1664 GET https://stats.iliad.it/matomo.js
net::ERR_BLOCKED_BY_CLIENT
common.min.js:558 GET https://acsbapp.com/apps/app/dist/js/app.js
net::ERR_BLOCKED_BY_CLIENT
app.js:22 GET https://cdn.acsbapp.com/config/
registrazione.iliad.it/config.json 404 (Not Found)
app.js:22 acsb: This website is not registered or its
license is expired.
// the synchronous cascade — every line below waits on the one above:
common.min.js:558 initAccessiway <-- the failing widget init
index.php:1610 (anonymous)
jquery.min.js:2 j -> fireWith -> ready -> S
(document.ready handler; the rest of the page
registration form, payment iframe, validation
all suspended behind a 404 from an a11y vendor)
A third-party accessibility-overlay widget — a vanity bolt-on, not a payment component, not an auth component — had its license lapse and its config 404. Because the page boots that widget synchronously inside its initialization chain (initAccessiway called straight out of common.min.js, before the form is interactive), the failure of a non-essential external dependency cascaded into a full denial of service of the registration funnel. The signup page for a telecom with eight figures of subscribers was, from where I sat, taken down by a SaaS billing problem at a company most of its engineers have never heard of.
This is the textbook third-party supply-chain failure, except nobody had to be malicious. No magecart skimmer, no compromised CDN, no typosquatted package. Just an expired invoice on a dependency that was given a synchronous seat in the critical path it had no business being in. Now imagine that same acsbapp.com origin had been compromised instead of merely unpaid. It was already executing arbitrary JavaScript on the registration page with no Subresource Integrity hash and no CSP allowlist constraining it. The expired license is the friendly version of this incident. There is an unfriendly version, and the architecture cannot tell the two apart.
WHAT THE PAGE TRUSTED, WITH WHAT GUARANTEE
-------------------------------------------------------
stats.iliad.it/matomo.js analytics no SRI, in init path
acsbapp.com/app.js a11y overlay no SRI, SYNC, single point of failure
jquery.min.js?v=<hash> framework ?v= cachebust, not integrity
common.min.js?v=<hash> glue orchestrates all of the above
-------------------------------------------------------
"?v=<hash>" is cache-busting. Subresource Integrity is a
DIFFERENT hash, in a DIFFERENT attribute, that the browser
actually verifies. This site confused the two. Most do.
-------------------------------------------------------
ERR_BLOCKED_BY_CLIENT showing up on matomo.js and the overlay is not, as the assistant kept insisting, the user's ad blocker "breaking the secure payment." It is a tracker and a vanity widget being blocked, while the things that matter sit elsewhere — which brings us to the one accidentally correct security observation in the entire four hours.
Cross-Origin Was the Only Thing That Saved the Payment
Deep in the transcript the assistant gives up on form.submit() and notes that the real card fields live in a separate cross-origin <iframe> owned by the payment service provider, so console script in the parent page cannot touch them. That is true. That is also the only layer in this entire flow that actually held — and Iliad didn't build it. Visa/the PSP did. The same-origin policy did. The browser did.
Sit with that. Strip the geo-block: trivial. Strip validation, errors, modals: trivial, the assistant did it for me. Force-post the form: trivial. The single thing standing between this site and a fully scripted hostile submission is a security boundary the site inherited from the platform by embedding a third-party iframe — not one it designed. Defense in depth is supposed to be layers you built. Here there is exactly one layer, it is load-bearing for the entire business, and it is on loan.
Why It Still Works — The General Case
Italy is the case study because it's where I was standing with the console open, but this is every legacy-jQuery, third-party-script-soup, server-rendered-then-bolted-on stack on the public web. DOM injection and its quieter cousin reflected XSS keep working on these sites for the same five reasons, every time:
