DEV Community

Cover image for A Closer Look at Software Supply Chain Attacks 2025: PyPI & npm Campaigns Compared
Luis Manuel Rodriguez Berzosa
Luis Manuel Rodriguez Berzosa

Posted on • Originally published at xygeni.io

A Closer Look at Software Supply Chain Attacks 2025: PyPI & npm Campaigns Compared

Xygeni recently observed a concerning pattern in the landscape of software supply chain attacks in 2025, emerging across two popular package managers: PyPI and npm.

As part of our ongoing threat intelligence efforts, Xygeni's Malware Early Warning (MEW) tool identified a distinct but methodologically similar instance of a malicious package uploaded to another registry previously.

This case highlights how threat actors are evolving and reusing attack techniques across ecosystems, especially through malicious Python packages and malicious npm packages, which increases the risk of malicious packages infiltrating open source supply chains.

Xygeni's MEW: Spotting the Similarities

Xygeni's MEW tool is built to detect and flag malicious packages, especially those linked to Software Supply Chain Attacks in 2025, as soon as they are published to open-source registries. It works by analyzing new packages for suspicious behavior.

In this instance, it identified two malicious Python packages (graphalgo on PyPI) and malicious npm packages (express-cookie-parser on npm).

Malicious Python and npm Packages in Software Supply Chain Attacks (2025)

Let’s explore the shared characteristics that triggered our alerts:

Typosquatting

They both tried to impersonate popular existing packages:

  • graphalgo (PyPI): “Python package for creating and manipulating graphs and networks.” Uploaded by a user named "larrytech" on June 13, 2025, this package presented itself as a PyPI alternative to the original non-malicious graphalgo project, which was later renamed graphdict.
  • express-cookie-parser (npm): This package imitated the well-known cookie-parser package, even mirroring its README.md. Such impersonation is a classic tactic to leverage existing trust.

Obfuscation as a First Line of Defense

Both packages obfuscated their malicious code within the original files.

  • For graphalgo, the obfuscated code was found within /utils/load_libraries.py.
  • In the case of express-cookie-parser, cookie-loader.min.js contained the obfuscated payload.

The obfuscation method was simple: repeated ZLib compression plus Base64 encoding. This clearly indicates concealment.

Xygeni MEW classified these as potential malware. During review, a deobfuscation script revealed the obviously malicious dropper stage.

Multi-Stage Payload Delivery and a Shared Origin Point

Both packages functioned as initial droppers, setting the stage for the real payload.

They shared a common external seed file URL:

This identical external seed file is a strong indicator of a shared threat actor. The file appears to contain environment variables (e.g., JWT_SECRET, PORT), misleading casual inspection.

Dynamic C2 Resolution with a DGA

A sophisticated tactic observed is the use of a Domain Generation Algorithm (DGA) to resolve the C2 server dynamically.

This technique, often seen in advanced supply chain attacks in 2025, relies on a SHA256 hash derived from the seed file’s content, combined with other hardcoded values.

In this case, the npm variant used a fixed value of:

Establishing Persistence

Both attackers dropped persistent startup scripts:

  • startup.py (PyPI)
  • startup.js (npm)

These were placed into Google Chrome user data directories across OSes (Windows, Linux, macOS).

Post-Execution Cleanup

Both malicious scripts performed cleanup operations:

  • Deleting initial dropper files (load_libraries.py, cookie-loader.min.js)
  • Modifying legitimate package files (__init__.py, index.js) to remove traces

Indicators of Compromise in Malicious Python and npm Packages

  • PyPI Package: graphalgo (published by larrytech, June 13, 2025)
  • npm Package: express-cookie-parser (specifically version 1.4.12)
  • Shared Seed URL:

  • Persistent Filenames: startup.py (Python), startup.js (JavaScript)

  • Common Paths:

    • Windows: AppData\Local\Google\Chrome\UserData\Scripts\
    • Linux: ~/.config/google-chrome/Scripts/
    • macOS: ~/Library/Application Support/Google/Chrome/Scripts/

How to Defend Against Malicious Packages in Software Supply Chain Attacks 2025

A Recommended Approach for Developers

This case shows how malicious Python packages and malicious npm packages can slip into trusted ecosystems. Early detection, tooling, and proactive hygiene are key.

Dependency Review

If your projects included these, remove them:

  • For PyPI: pip uninstall graphalgo
  • For npm: npm uninstall express-cookie-parser

System Hygiene

Run full scans and manually check Chrome user data directories for startup.py or startup.js.

Proactive Security Measures

  • Assess New Dependencies: Thoroughly review new packages before integrating them.
  • Integrate Security Tools: Use solutions like Xygeni’s MEW and SCA tools.
  • Least Privilege: Apply least privilege to development environments.
  • Network Awareness: Monitor outbound connections for DGA-based C2 traffic.

Key Components of load_libraries.py (from graphalgo)

python
import os
import sys
import subprocess
import base64
import hashlib
import time
from urllib.request import urlopen, Request

url_b64 = "aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2pvaG5zOTIvYmxvZ19hcHAvcmVmcy9oZWFkcy9tYWluL3NlcnZlci8uZW52LmV4YW1wbGU="
remove_url = base64.b64decode(url_b64).decode()
Enter fullscreen mode Exit fullscreen mode

Highlighted functions:

  • download_remote_content() – Fetches files from URLs.
  • run_process() – Executes scripts discreetly.
  • get_output_file_path() – Finds location for persistent startup.py.
  • remove_self() – Cleans up malicious files after execution.
  • simple_shift_encrypt() / simple_shift_decrypt() – Custom obfuscation for C2 comms.
  • load_libraries() – Orchestrates multi-stage execution.

Aftermath

As with other malicious packages flagged by MEW, Xygeni’s dependency firewall immediately protected users. After confirmation, both registries were notified and the packages were removed.

Top comments (0)