❄️A Five-Minute UI Feature That Became an XSS Time Bomb
Can a simple script — a trivial visual effect — put your application at risk?
Oh yes. And you might not even realize how.
What’s more, small, innocent-looking pieces of code can turn into shiny, colorful time bombs. How is that possible?
Let me tell you a hypothetical story.
❄️ The Snow Begins to Fall
Imagine you develop a website, a shop, or a web application.
December comes around. Lights, trees, decorations everywhere. The holiday mood starts to get to you.
You — or one of your stakeholders — asks for a small seasonal touch.
“Maybe some falling snow?” ❄️
You’re excited and immediately jump on this very creative task.
But wait.
The backlog is overflowing. Deadlines are screaming. And suddenly you remember something important:
you’re lazy. 😉
You’re obviously not going to write this from scratch.
So you do what all of us do:
you open CodePen, GitHub, maybe Stack Overflow… and copy a random snippet.
❄️ The Innocent Snow Script
<script>
/**
* ❄️ Simple snow effect
* Source: random blog / CodePen
*/
const snowflakes = ["❄️", "❅", "❆"];
for (let i = 0; i < 40; i++) {
const el = document.createElement("div");
el.className = "snowflake";
// ❌ copied straight from the internet
el.innerHTML =
snowflakes[Math.floor(Math.random() * snowflakes.length)];
el.style.left = Math.random() * 100 + "vw";
el.style.fontSize = 12 + Math.random() * 24 + "px";
el.style.animationDuration = 5 + Math.random() * 5 + "s";
document.body.appendChild(el);
}
</script>
Five minutes later — boom — you have a beautiful falling snow effect.
You don’t even put it up for a proper code review.
Or someone glances at it briefly, because hey — it’s just a visual effect, right?
Security tests?
Why would anyone test falling snow?
🎁 Congratulations — You’ve Just Shipped an XSS
The problem is right here:
el.innerHTML = snowflake;
XSS (Cross-Site Scripting) happens when untrusted data is injected into the DOM in a way that allows it to be executed as HTML or JavaScript.
Important clarification: at this exact moment, this code is not yet an active XSS vulnerability, because snowflake comes from a hard-coded, fully trusted array.
However, the dangerous pattern is already in place — and that’s what turns this into a time bomb.
For now, nothing bad happens.
Everything looks fine.
⏳ Time Passes…
January comes. Management decides it’s time to turn the visual effect off.
“But don’t remove it!”
“We’ll need it next year!”
Or maybe even earlier — spring is coming, so instead of snowflakes, let flower petals fall 🌸.
A new task lands in the backlog.
Another developer picks it up and thinks:
“I’m not going to toggle this every few months.
Let management control it themselves — and maybe even choose the icons.”
So they add configuration.
Maybe via a CMS. Maybe via a remote endpoint.
🌐 The “Small Improvement”
function fetchSeasonalConfig() {
return Promise.resolve({
enabled: true,
snowflake: "❄️"
});
}
fetchSeasonalConfig().then(config => {
if (!config.enabled) return;
for (let i = 0; i < 30; i++) {
const el = document.createElement("div");
el.className = "snowflake";
// ❌ still innerHTML
el.innerHTML = config.snowflake;
document.body.appendChild(el);
}
});
Now the doors are wide open.
At this point, the value crosses a trust boundary: it no longer comes from a constant defined in code, but from an external source that can change independently of the application logic.
An attacker doesn’t need anything fancy:
- a compromised CMS account
- a misconfigured role
- a WYSIWYG editor
- a copied snippet from Notion or email
- an intercepted or modified API response
For example:
snowflake: '<img src=x onerror="alert(\'XSS 🎄\')">'
And that’s it.
Full XSS in your app.
🤔 “But I Use a Modern Framework!”
Some of you might be thinking now:
“Come on. This applies to old jQuery sites.
I use a modern framework — React / Angular / Vue — and it protects me from XSS.”
Nothing could be further from the truth.
⚠️ Frameworks Only Protect What They Render
React and Angular do escape content by default — but only inside their rendering system.
The moment you use:
innerHTML-
dangerouslySetInnerHTML(React) -
[innerHTML]orbypassSecurityTrustHtml(Angular) - or a plain JS script running outside the framework
…you’re on your own.
And guess what?
That snow script?
It often lives outside the framework, in index.html, loaded as a “small visual effect”.
Frameworks don’t sandbox random JavaScript files.
✅ How This Could Have Gone Differently
All of this could have been avoided with one simple change:
el.textContent = snowflake;
Or by:
- creating DOM nodes explicitly instead of injecting HTML
- sanitizing HTML with a well-maintained library like DOMPurify (properly configured, with a strict allowlist)
- clearly defining a security boundary: everything external is untrusted
- treating “small UI features” with the same security mindset as core functionality
Defense-in-depth measures like Content Security Policy (CSP) can also reduce the impact — but they don’t fix unsafe DOM APIs.
🎄 Final Thoughts
Did this exact story happen?
No 😉
Have I heard dozens of very similar ones?
Absolutely.
Remember: no feature is too small to skip proper code review and security testing.
The devil is in the details.
🎁 Happy Holidays
Wishing you happy and peaceful holidays —
the kind you can give yourself by being just a little more careful about what you copy from the internet ❄️✨

