DEV Community

Ronaldo Modesto
Ronaldo Modesto

Posted on

How Supply Chain Attacks Work

Acesse a versão em português aqui: Clique Aqui

Hi everyone.

Today I want to share a little more knowledge about a type of attack that has become increasingly frequent: the Supply Chain Attack.

Let's see how this works in the npm ecosystem and what we can do to mitigate this risk.
Remember that I've only used npm as an example here, but this type of attack can occur in other package managers as well!

Here you'll find a tool I developed to mitigate this type of attack. Safeinstall

If you want to see how the tool works, take a look here SafeInstall


Introduction

How many times a day do you run npm install? For most JavaScript and Node.js developers, the answer is: many. This seemingly innocent routine — installing a dependency to solve a problem — hides an attack vector increasingly exploited by cybercriminals: the supply chain attack.

In this article, we explore what these attacks are, how they work in practice using a real demonstration project, what the consequences would be in production environments, and how you can protect yourself.

The project used in the article can be found here: Example Project


What is a Supply Chain Attack?

A supply chain attack occurs when an attacker compromises a component that is part of the software supply chain — something that developers or systems trust and use without question. In the npm ecosystem, this materializes mainly through:

  1. Malicious packages created from scratch to appear legitimate
  2. Compromised legitimate packages (abandoned maintenance, hacked account, typosquatting)
  3. Lifecycle scripts that execute automatically during npm install

The critical point is that the developer does not need to do anything beyond installing the package. No need to open a suspicious file, click a link, or run an unknown binary. The simple act of adding a dependency to package.json and running npm install can be enough to compromise the machine, repository, or infrastructure.


Lifecycle Scripts: The Entry Point

npm defines various scripts that run at specific moments in a package's lifecycle:

Script When it runs
preinstall Before the package is installed
install During installation
postinstall Immediately after installation — preferred target for attacks
preuninstall Before uninstalling
postuninstall After uninstalling
prepublish Before publishing to the npm registry

Anyone who runs npm install will execute these scripts automatically, with no clear warning. That is where the danger lies.


Demonstration Project: Structure and Code

To illustrate the attack vector, we built an educational project consisting of:

  1. "Malicious" package — a package that appears useful but runs code during installation
  2. Victim project — a project that simply depends on that package

Malicious Package Structure

The package's package.json defines the scripts that will be executed:

{
  "name": "utilidades-uteis",
  "version": "1.0.0",
  "description": "Pacote útil que parece legítimo mas executa código no post-install",
  "main": "index.js",
  "scripts": {
    "postinstall": "node postinstall.js",
    "preinstall": "node preinstall.js"
  },
  "keywords": ["utility", "helper"],
  "author": "Atacante Anônimo",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

Note that postinstall and preinstall point to Node.js scripts. These scripts run automatically during installation.

preinstall.js Script

This script runs before the package is installed:

/**
 * PREINSTALL - Runs BEFORE the package is installed
 * Another phase where malicious code can execute
 */

console.log('\u001b[35m[preinstall]\u001b[0m This script runs even before the package is installed!');
Enter fullscreen mode Exit fullscreen mode

In a real attack, initial data collection or environment preparation could happen here.

postinstall.js Script — The Heart of the Attack

Here is the script that simulates exfiltration of sensitive data:

const fs = require('fs');
const path = require('path');
const os = require('os');

// Simulates what an attacker COULD collect (only shows, does not send)
const dadosSensiveis = {
  executadoEm: new Date().toISOString(),
  usuario: os.userInfo().username,
  diretorioAtual: process.cwd(),
  platform: process.platform,
  nodeVersion: process.version,
  // A real attacker would try to read:
  // env: process.env,  // Tokens, passwords, API keys
  // arquivos: fs.readdirSync(process.env.HOME)
};

// Creates "proof" file - in real attack would be sent to server
const arquivoProva = path.join(process.cwd(), 'PROVA_ATAQUE_SUPPLY_CHAIN.json');
fs.writeFileSync(arquivoProva, JSON.stringify(dadosSensiveis, null, 2), 'utf8');

console.log(`\u001b[31m[SIMULATED ATTACK]\u001b[0m Data collected saved to: ${arquivoProva}`);
console.log('\nIn a REAL attack, this would be sent to the attacker\'s server.\n');
Enter fullscreen mode Exit fullscreen mode

Example of a file created after the script runs:

exfiltrated information image

And when running the application, the user doesn't even notice what happened:

Image showing that the user will not see the malware execution

In a real malicious version, the attacker would replace fs.writeFileSync with an HTTP call (e.g., using https.request) to send this data to a server under their control. The package also exposes a legitimate module (index.js) that does something useful — making the package plausible and reducing suspicion.

Attack Flow

1. Developer: npm install utilidades-uteis
2. npm downloads the package
3. npm runs preinstall  → malicious code #1
4. npm runs postinstall → malicious code #2 (collects data)
5. Package installed normally
6. Developer does not realize they have been compromised
Enter fullscreen mode Exit fullscreen mode

Consequences in Real Environments

What could happen if this were a real attack? The consequences vary depending on the victim's context.

1. Exfiltration of Credentials and Secrets

  • Environment variables (process.env): API tokens (AWS, GitHub, Stripe), database keys, passwords
  • .env files: credentials in plain text across multiple projects
  • Configuration files: ~/.npmrc, ~/.aws/credentials, ~/.ssh/config

Impact: Access to cloud accounts, databases, private repositories, and third-party systems.

2. Theft of SSH Keys and Certificates

  • Reading ~/.ssh/ (private keys, known_hosts)
  • Using the keys to access servers, GitHub, private repositories

Impact: Server intrusion, cloning of private repositories, malicious commits in the victim's name.

3. Cryptojacking

  • Running a cryptocurrency miner in the background
  • Consuming the victim's machine or server CPU and power

Impact: High infrastructure costs, performance degradation, possible violation of cloud usage policies.

4. Backdoors and Persistence

  • Installing remote access tools
  • Adding scheduled tasks or startup scripts

Impact: Prolonged control of the machine, espionage, preparation for future attacks.

5. Modification of Other Packages

  • Altering code in node_modules of other dependencies
  • Injecting backdoors into libraries used in production

Impact: Compromise at scale, propagation of the attack to all application users.

6. In CI/CD Environments

  • Access to pipeline secrets (tokens, credentials)
  • Ability to modify build artifacts or Docker images
  • Deployment of compromised versions to production

Impact: Compromise of the entire delivery chain, from build to production.


Documented Real-World Cases

Case Year Description
event-stream 2018 Package with ~2M weekly downloads. Malicious code added to steal Bitcoin wallets.
ua-parser-js 2021 Popular library compromised; ran cryptocurrency miner.
coa and rc 2021 Typosquatting; packages stole environment variables and sent them to a remote server.
node-ipc 2022 Added code that modified files on machines of developers from certain geographic regions.

Protection Measures

  1. Use --ignore-scripts when possible: npm install --ignore-scripts
  2. Audit dependencies: npm audit, npm audit fix
  3. Verify scripts: npm view package-name before installing
  4. Specialized tools: Socket.dev, Snyk for detecting suspicious behavior
  5. Keep package-lock.json in version control and review changes
  6. Verify package provenance: download counts, active maintenance, open repository

Conclusion

The npm ecosystem is extremely convenient, but that convenience carries risks. The seemingly trivial act of npm install can execute arbitrary code on your machine. Awareness of supply chain attacks and adopting security best practices are essential to reduce the attack surface and protect projects and infrastructure.

The demonstration project is available so you can test the flow in a controlled environment and understand how these attacks work in practice.


This article was written for educational purposes. The demonstration project contains only simulated code and does not perform any real malicious actions.

Top comments (0)