❄️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>
Enter fullscreen mode

Exit fullscreen mode

Five minutes later — boom — you have a beautiful falling snow effect.

Falling JavaScript snow and heading

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;
Enter fullscreen mode

Exit fullscreen mode

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);
  }
});
Enter fullscreen mode

Exit fullscreen mode

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 🎄\')">'
Enter fullscreen mode

Exit fullscreen mode

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] or bypassSecurityTrustHtml (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;
Enter fullscreen mode

Exit fullscreen mode

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 ❄️✨



Source link