# Infostealer

> An infostealer is malware that copies secrets a machine can reach, like passwords, session cookies, and cloud or registry tokens, and sends them to an attacker.

Also known as: infostealer malware, information stealer, info stealer, credential stealer, stealer malware, stealer logs, password stealer

Source: https://socket.dev/glossary/infostealer

Last updated: 2026-10-27

An infostealer is malware that searches a computer for secrets, such as saved passwords, session cookies, cloud keys, package registry tokens, and crypto wallets, and sends them to an attacker. An infostealer usually needs no exploit: it runs as the user and reads what the user can read. Infostealers were also the payload of npm's largest worm campaigns.

Infostealers, also called information stealers or credential stealers, come from two markets. The commodity market sells desktop infostealers like [RedLine](https://socket.dev/blog/redline-and-meta-malware-operations-disrupted) and Lumma as a subscription and spreads them through fake downloads and cracked software. The second market ships the same capability inside [malicious packages](https://socket.dev/glossary/malicious-package) on npm and PyPI, where it runs during install, import, or interpreter startup on developer laptops and CI runners. That second group steals the credentials that build and publish software, so each victim can become the next distribution point.

Our position: the useful question is not "is this machine infected?" but "what could code running as this user read?" An infostealer can finish its work in seconds. Removing the package afterward does not un-steal anything. The controls that matter stop the code before it runs and shrink what it can read when one gets through.

## What is infostealer malware and how does it work?

Infostealer malware works by running under a user's account and copying the secrets that account can already read. No privilege escalation is required, because desktop operating systems isolate users from each other, not a user from their own programs.

Browser cookie storage on Windows shows the trust assumption clearly. Chrome encrypted its cookie database with DPAPI, the Windows Data Protection API, which ties the key to the user's login. Will Harris of the Chrome Security Team [wrote in July 2024](https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html) that "the DPAPI does not protect against malicious applications able to execute code as the logged in user." Any program the user ran could ask Windows to decrypt the cookie database. Chrome 127 added App-Bound Encryption, which ties decryption to Chrome's identity through a service running with system privileges. An infostealer now has to gain system privileges or inject code into Chrome, which is noisier but still possible.

Developer credentials usually have no such layer. `~/.aws/credentials`, `~/.npmrc`, and `.env` files are plaintext. File permissions keep other users out, and every process the owner runs can still read them, including a dependency's install script.

Most infostealer runs follow five stages. The trigger gets the code executed. Fingerprinting decides whether the host is worth the noise, often by checking for CI variables or the operating system. Collection reads files, environment variables, and local metadata endpoints. Packaging compresses and usually encrypts the haul. Exfiltration sends it out over a channel chosen to look like normal traffic. Some families add persistence, a second-stage loader, or self-propagation.

> Figure 1. One infostealer run, stage by stage, for three payloads Socket analyzed. Select a payload to switch. The table below lists the same details.
>
> The five stages of an infostealer run, compared across three payloads Socket analyzed: LiteLLM 1.107.0, node-ipc, and the TanStack Mini Shai-Hulud worm

| Stage | LiteLLM 1.107.0 (PyPI, Oct 2026) | node-ipc 9.1.6, 9.2.3, 12.0.1 (npm, May 2026) | TanStack router packages (npm, May 2026) |
| --- | --- | --- | --- |
| Trigger | cook.pth and sitecustomize.py run at every Python startup | Obfuscated code appended to node-ipc.cjs runs on require() | Adds router\_init.js plus an optional git dependency whose prepare script runs a second payload at install |
| Fingerprint | Host and network info; checks for a once-only marker file | uname -a, /etc/hosts, OS details | CI platform, OS, and runtime checks gate collection |
| Collect | 16 modules: cloud, SSH and GPG keys, npm and PyPI config, browser databases, AI tool auth files | 127 path patterns on Linux, 113 on macOS, plus the full environment | GitHub Actions secrets, AWS IMDS, Secrets Manager and SSM, Vault, Kubernetes tokens |
| Package | JSON encrypted with AES-256-CBC, key wrapped with RSA-4096 | In-memory tar, gzip, HMAC-signed chunks | Session messenger protocol embedded in a 2.3 MB obfuscated file |
| Exfiltrate | HTTP POST to a lookalike domain path named /healthcheck | Roughly 29,400 DNS TXT queries per 500 KiB archive | Session P2P network, which looks like encrypted chat traffic |
| Extra | Hourly remote Python loader | None observed | Self-republishes via stolen OIDC tokens; persists in .claude/ and .vscode/ |

The exfiltration column is where detection usually fails. The [LiteLLM 1.107.0 compromise](https://socket.dev/blog/litellm-compromise) posted its encrypted archive to a path named `/healthcheck` on a domain resembling the project's own. The [node-ipc compromise](https://socket.dev/supply-chain-attacks/node-ipc) used no HTTP at all and leaked data as DNS TXT lookups under a fake `bt.node.js` zone. None of the three announces itself as exfiltration in network logs.

## What data does infostealer malware collect?

Infostealer malware collects anything that authenticates its victim: passwords, session cookies, tokens, private keys, and wallet seeds. Commodity families focus on browsers and wallets. Package-borne families focus on developer and cloud credentials, because those are what a build machine holds.

| Target | Where it typically lives | Why attackers want it |
| --- | --- | --- |
| Browser passwords and cookies | Chromium Login Data and Cookies databases, Firefox logins.json and key4.db | A session cookie skips the password and the MFA prompt |
| Crypto wallets | Wallet files and browser extension storage | Direct, irreversible theft |
| Package registry tokens | ~/.npmrc, ~/.pypirc, NPM\_TOKEN | Publish rights over every package the victim maintains |
| Source control | ~/.git-credentials, GitHub CLI token, GITHUB\_TOKEN in CI | Repository write access and CI secrets |
| Cloud credentials | ~/.aws/credentials, ~/.kube/config, the instance metadata endpoint at 169.254.169.254, /var/run/secrets/kubernetes.io/serviceaccount/token | Cloud control plane access, often at the build role's privileges |
| SSH and signing keys | ~/.ssh/, GPG keyrings | Server access and the ability to sign as the victim |
| Environment and dotfiles | Process environment, .env, .env.production | Whatever the application or pipeline was given |
| AI coding tools | ~/.claude/.credentials.json, ~/.codex/auth.json, ~/.gemini/oauth\_creds.json, ~/.cursor/mcp.json | Billable model access and agents that can act on the user's behalf |

Session cookies explain why infostealers outlast password resets. A session cookie is proof that a login, including any MFA step, already happened. Replaying it from another machine often works until the session expires.

Stolen passwords also stay useful for years. In the 2024 Snowflake customer data thefts, [Mandiant found](https://cloud.google.com/blog/topics/threat-intelligence/unc5537-snowflake-data-theft-extortion) credentials harvested by VIDAR, RISEPRO, REDLINE, RACOON STEALER, LUMMA, and METASTEALER. Mandiant and Snowflake notified about 165 potentially exposed organizations. At least 79.7% of the accounts used had prior credential exposure. The oldest infostealer infection tied to a used credential dated to November 2020. The affected accounts had no MFA.

Package-borne infostealers read far more than browsers. The node-ipc payload carried 127 file patterns for Linux and 113 for macOS, covering more than a dozen cloud and SaaS platforms, Terraform state, Kubernetes, Docker, and shell histories. The LiteLLM payload added authentication files for Claude, Codex, Gemini, Cursor, and other AI coding tools. The [Nx compromise](https://socket.dev/blog/nx-packages-compromised) in August 2025 went further and prompted locally installed AI CLIs to search the disk for wallet and secret files.

You can see your own exposure without running anything malicious. The script below prints the names of credential files and secret-looking [environment variables](https://socket.dev/glossary/environment-variables) visible to the current user. It never prints a value.

```sh
#!/bin/sh
# Lists what an install-time infostealer running as you could reach.
# Prints names and paths only. Never prints a secret's value.
for p in ~/.npmrc ~/.yarnrc.yml ~/.pypirc ~/.netrc ~/.git-credentials \
         ~/.aws/credentials ~/.azure ~/.config/gcloud ~/.kube/config \
         ~/.docker/config.json ~/.ssh ~/.config/gh ~/.claude ~/.codex \
         ~/.gemini ~/.cursor/mcp.json ./.env; do
  [ -e "$p" ] && echo "file  $p"
done
env | cut -d= -f1 | grep -Ei 'token|secret|key|pass|auth|cred' | sed 's/^/env   /'
```

We ran it in a test home directory seeded with three common files and the variables a CI job typically carries:

```text
file  /home/dev/.npmrc
file  /home/dev/.aws/credentials
file  /home/dev/.ssh
file  ./.env
env   GITHUB_TOKEN
env   NPM_TOKEN
env   AWS_SECRET_ACCESS_KEY
```

Every line is something a `postinstall` script in that job could have sent out. The list also omits the cloud metadata endpoint, which needs no file at all: on a cloud VM, including a self-hosted CI runner, any process can request the instance role's temporary credentials from `169.254.169.254`.

## How do infostealers get into npm and PyPI packages?

Infostealers get into npm and PyPI packages in two ways: attackers publish lookalike packages, or they take over real ones. Takeovers matter more, because the victim already trusts the package and installs it through a normal version range. The malicious node-ipc versions came from a dormant maintainer account, most likely taken over through an expired email domain. The [TanStack compromise](https://socket.dev/blog/tanstack-npm-packages-compromised-mini-shai-hulud-supply-chain-attack) and LiteLLM's malicious container images came out of the projects' own CI pipelines.

Once a package is installed, the infostealer needs a trigger. Package managers offer several, and they fail differently. The [axios compromise](https://socket.dev/blog/axios-npm-package-compromised) used a plain `postinstall` script in a new dependency, `plain-crypto-js`. The [Miasma](https://socket.dev/supply-chain-attacks/miasma-mini-shai-hulud-supply-chain-attack) "Phantom Gyp" waves used a `binding.gyp` file, which makes npm run a native build even when `package.json` declares no script.

| Trigger | How it runs | Example | Blocked by default in npm v12? |
| --- | --- | --- | --- |
| preinstall / install / postinstall | npm runs the script during install | The axios compromise dropper in plain-crypto-js | Yes |
| Implicit node-gyp rebuild | A binding.gyp file triggers a native build with no script in package.json | The Miasma "Phantom Gyp" waves | Yes |
| Git dependency prepare | npm runs prepare when installing from git | @tanstack/setup, injected as an optional git dependency | Yes, git dependencies need --allow-git |
| require() or import | Top-level module code runs on load | node-ipc's appended .cjs code | No |
| Python startup hooks | site executes import lines in .pth files and imports sitecustomize | LiteLLM's one-line cook.pth | Not applicable; runs on every interpreter start |

Default-on [install scripts](https://socket.dev/glossary/postinstall-scripts) were a design mistake, and the ecosystem has started to correct it. [pnpm 10](https://socket.dev/blog/pnpm-10-0-0-blocks-lifecycle-scripts-by-default) blocked dependency lifecycle scripts by default in January 2025. [npm v12](https://socket.dev/blog/npm-12) followed in July 2026 and, as of October 2026, its `latest` release is 12.2.1. It skips dependency scripts unless the project allows them. We confirmed the behavior with a test package whose only content is a `postinstall` script:

```text
$ npx -y npm@12.2.1 install ../dep/demo-dep-1.0.0.tgz

added 1 package, and audited 2 packages in 602ms

found 0 vulnerabilities
npm warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts   demo-dep@1.0.0 (postinstall: node -e "console.log('postinstall ran, can read', Object.keys(process.env).length, 'env vars')")
npm warn install-scripts
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve <pkg>` to allow.
```

The same install under npm 10.9.9 ran the script, which printed `postinstall ran, can read 219 env vars`. That count is our test shell's entire environment plus the variables npm adds.

The new defaults close the install-time triggers and leave two others open. node-ipc needed no install script: its payload ran when an application called `require("node-ipc")`. LiteLLM needed no import at all. Python's `site` module processes `.pth` files in `site-packages` at startup. The [Python documentation](https://docs.python.org/3/library/site.html) says executable lines "are always run at Python startup ... regardless of whether a particular module is actually going to be used." You can reproduce it in a fresh virtual environment:

```sh
python3 -m venv venv
SP=$(venv/bin/python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
cat > "$SP/demo.pth" <<'EOF'
import os, sys; "DEMO_PTH" in os.environ or (os.environ.__setitem__("DEMO_PTH", "1"), sys.stderr.write(f"demo.pth ran: argv={sys.argv}\n"))
EOF
venv/bin/python -c pass        # demo.pth ran: argv=['-c']
venv/bin/pip --version         # demo.pth ran: argv=['venv/bin/pip', '--version']
venv/bin/python -I -c pass     # demo.pth ran: argv=['-c']
venv/bin/python -S -c pass     # (nothing)
```

Two details from that run matter for incident response. Running `pip` to inspect or uninstall a poisoned package executes the payload again. Isolated mode (`-I`) does not help; only `-S`, which disables the `site` module, stops it. The `DEMO_PTH` guard mirrors the `PRIVCOLLECT_ACTIVE` variable LiteLLM's payload set to avoid collecting twice in one process. Python 3.15, released October 9, 2026, deprecates `import` lines in `.pth` files but still runs them. Its replacement, the `.start` entry point file, also runs at every startup.

Our view: turning off install scripts is necessary and not sufficient. The decision has to be made on what is inside the package before it reaches `node_modules` or `site-packages`, not on which hook its metadata declares.

## Why do infostealers spread like worms in package registries?

Infostealers spread like worms in package registries because the secrets they steal include the credentials that publish packages. A stolen npm token or CI identity lets the malware push a new version of every package its victim maintains.

[Shai-Hulud](https://socket.dev/glossary/shai-hulud), the first known self-propagating npm worm, made that loop real in September 2025. The [@ctrl/tinycolor compromise](https://socket.dev/blog/tinycolor-supply-chain-attack-affects-40-packages) that started it ran TruffleHog, a legitimate secret scanner, to sweep the host for npm, GitHub, and cloud credentials. It then used the stolen npm tokens to inject itself into the maintainer's other packages and republish them. Our follow-up counted nearly 500 affected npm packages within a day.

The TanStack compromise in May 2026 removed the need for a stolen token. Attacker code that ran inside a [GitHub Actions](https://socket.dev/glossary/github-actions-security) workflow pulled an OIDC token from the runner and published through the project's [trusted publishing](https://socket.dev/glossary/npm-trusted-publishing) binding. The malicious versions carried valid Sigstore provenance. Provenance proved which workflow published the package, and that workflow was the attacker's foothold. As of October 27, 2026, our [Mini Shai-Hulud](https://socket.dev/supply-chain-attacks/mini-shai-hulud) campaign page lists 1,069 affected artifacts across 507 packages.

The LiteLLM incident shows the same loop across months. Each stolen credential unlocked the next stage.

> Figure 2. The October 2026 LiteLLM compromise in UTC. Each set of stolen credentials opened the next stage. Sources: Socket's analyses and LiteLLM's investigation. The table below lists the same events.
>
> Timeline of the October 2026 LiteLLM compromise, showing how each stolen credential unlocked the next stage, from a leaked key in May to a malicious PyPI release on October 21

| When (UTC) | Event | What it gave the attacker |
| --- | --- | --- |
| May 2026 | Agent Platform master key committed to a public repository and never revoked, per LiteLLM's investigation | A GitHub organization owner's token |
| Oct 10, 22:11 | Throwaway branch runs .github/workflows/cache.yml, posting six org secrets to webhook.site | GH\_TOKEN, a GitHub App ID and private key, three API keys |
| Oct 12, 12:16 | Second workflow binds five environments and takes 14 more secrets | Model provider keys, database and Redis passwords, Playwright session cookies |
| Oct 21, 10:51 to 11:06 | Release workflow builds container images from the lookalike Berri-AI/litellm source | Images carrying the infostealer, with build provenance attached |
| Oct 21, 11:39:51 | litellm==1.107.0 wheels published to PyPI | Code execution on every Python start in affected environments |
| Oct 21, 11:46:30 | Socket flags the wheels as malware | Detection, 6 minutes 39 seconds after publish |

Our [timeline analysis](https://socket.dev/blog/litellm-attack-timeline) found the October 10 workflow ran 11 days before the malicious release. The October 12 run's secrets did not overlap with the first run's, which reads as reconnaissance followed by broader collection. LiteLLM says its audit logs link the `GH_TOKEN` stolen on October 10 to the approval of the October 21 release. The payload delivered on October 21 then targeted the credentials of the next tier of victims: cloud roles, provider keys, and AI tool sessions on every machine that installed the release.

Stolen credentials also feed other crime. In March 2026, the Vect ransomware group announced a [partnership with TeamPCP](https://socket.dev/blog/teampcp-partners-with-vect-targeting-oss-supply-chains), the actor behind many 2026 package compromises, to turn that access into ransomware. Infostealer output is initial access for whoever buys it.

## Desktop infostealers versus package infostealers

Desktop and package infostealers copy the same kinds of secrets. Their delivery, targets, and timing differ enough that one defense does not cover both.

The desktop market is industrial. RedLine subscriptions started at $150 a month before Dutch police [disrupted it in October 2024](https://socket.dev/blog/redline-and-meta-malware-operations-disrupted). [Microsoft found](https://blogs.microsoft.com/on-the-issues/2025/05/21/microsoft-leads-global-action-against-favored-cybercrime-tool/) more than 394,000 Windows computers infected with Lumma between March 16 and May 16, 2025. Desktop operators also target developers directly: a [fake Corepack site](https://socket.dev/blog/fake-corepack-site-distributes-infostealer-and-proxyware) served an infostealer to people searching for the tool after Node.js stopped bundling it.

| Dimension | Desktop infostealer (RedLine, Lumma) | Package infostealer (Shai-Hulud, node-ipc, LiteLLM 1.107.0) |
| --- | --- | --- |
| Delivery | Phishing, malvertising, cracked software, fake installers | Malicious or compromised npm, PyPI, and other registry releases |
| Trigger | The user runs an executable | Install script, import, or interpreter startup, often in CI |
| Platform | Mostly Windows, some macOS | Linux, macOS, and Windows; CI runners and developer laptops |
| Main targets | Browser passwords and cookies, wallets | Registry, cloud, CI, SSH, and AI tool credentials |
| Business model | Malware-as-a-service subscriptions; logs resold in bulk | Direct use of access, worm propagation, resale, ransomware partnerships |
| Time to impact | Logs sold and used over months or years | Republishing within minutes or hours |
| Primary defense | Phishing-resistant MFA, short sessions, endpoint detection | Pre-install package analysis, release cooldowns, short-lived scoped CI credentials |

The verdict: treat developer and CI machines as a separate threat model. Endpoint protection and MFA address the desktop family, and they do almost nothing when a trusted dependency reads `NPM_TOKEN` during `npm install` in an ephemeral runner. Package infostealers have to be stopped before the package executes, and limited by what the build environment exposes.

Infostealers differ from [remote access trojans (RATs)](https://socket.dev/glossary/remote-access-trojans-rats). An infostealer grabs secrets and leaves. A RAT gives the attacker an interactive foothold. The axios dropper installed a RAT; most of the other 2026 compromises in this article chose the infostealer, because a stolen token is more portable than a shell on one machine.

## Defending against infostealers, and what each control misses

Each control below closes part of the path. None closes all of it. A vulnerability scanner would have reported nothing for any release in this article, because malware does not get a CVE.

**Block known-bad versions before install.** [Socket Firewall](https://docs.socket.dev/docs/socket-firewall-free) runs as `sfw npm install` (also pnpm, yarn, pip, uv, and cargo) and blocks requests for package artifacts that Socket has confirmed as malware, at any depth in the tree. It only helps once a version is flagged. Our AI scanner flagged the LiteLLM wheels 7 minutes after publication, the TanStack artifacts within 6 minutes, and node-ipc within roughly 3 minutes. An install inside that window, or of a version nobody has analyzed, still goes through.

**Wait before adopting new versions.** A [dependency cooldown](https://socket.dev/glossary/dependency-cooldown) refuses versions younger than a set age. [pnpm 11](https://socket.dev/blog/pnpm-11-adds-new-supply-chain-protection-defaults) defaults `minimumReleaseAge` to 1,440 minutes. Compromised releases of popular packages are often pulled within hours: npm removed the malicious Nx versions about four hours after the first one appeared. A one-day cooldown skips windows like that. It also delays urgent security patches, can be overridden per project, and does nothing against a payload that waits longer than the cooldown.

**Turn off install-time execution.** npm v12 and pnpm 10 both skip dependency scripts unless approved. Blocking scripts removes the most common trigger and leaves import-time code and Python startup hooks untouched.

**Shrink what the install step can read.** Run dependency installation in a job that holds no publish token and no cloud role. Prefer short-lived, narrowly scoped credentials to long-lived tokens in environment variables. Keep `id-token: write` on the publishing job only. TanStack shows the limit: attacker code running in a job that can mint an OIDC token can mint one too.

**Respond by rotating, not uninstalling.** Treat every secret readable by an affected process as stolen, then revoke it from a clean machine. Look for persistence the payload left outside the package: TanStack's worm wrote itself into `.claude/settings.json` hooks and `.vscode/tasks.json`. Do not rely on a clean lockfile as proof of safety. Tools run through `npx` or global installs never appear in a project's lockfile, as our [analysis of the axios compromise's blast radius](https://socket.dev/blog/hidden-blast-radius-of-the-axios-compromise) showed.

The credential an infostealer wants most is no longer a bank password. It is the token that publishes software, or the agent session that can write and run code. Every new tool that drops a long-lived credential in a home directory adds a line to some infostealer's target list. The durable fix is credentials that expire in minutes, scoped to one job, and unreadable by the code you install.
