Back to glossary

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

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 and Lumma as a subscription and spreads them through fake downloads and cracked software. The second market ships the same capability inside malicious packages 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 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.

  1. Triggercook.pth (one line: import cook) and sitecustomize.py run at every Python start
  2. FingerprintRecords host and network info; a /tmp/.pc_once_* marker and PRIVCOLLECT_ACTIVE stop repeat collection
  3. Collect16 modules: cloud and Kubernetes, SSH and GPG keys, npm and PyPI config, browser databases, Claude, Codex, Gemini, and Cursor auth files
  4. PackageJSON encrypted with AES-256-CBC; key wrapped with RSA-4096
  5. ExfiltrateHTTP POST to www.litellm[.]dev/healthcheck
  6. ExtraDetached loader fetches and runs remote Python, retrying hourly
  1. TriggerObfuscated code appended to node-ipc.cjs runs on require("node-ipc"); no install script
  2. Fingerprintuname -a, /etc/hosts, Node.js OS details
  3. Collect127 path patterns on Linux, 113 on macOS, plus every environment variable
  4. PackageIn-memory tar, gzip, HMAC-SHA256 signed chunks
  5. ExfiltrateDNS TXT queries under bt.node.js; about 29,400 queries per 500 KiB archive
  6. ExtraNone observed: no persistence, no second stage
  1. TriggerAdds router_init.js plus an optional git dependency, @tanstack/setup, whose prepare script runs a second payload at install
  2. FingerprintCI platform, operating system, and JavaScript runtime gate what it collects
  3. CollectGitHub Actions secrets, AWS IMDSv2, Secrets Manager and SSM, Vault, Kubernetes service account tokens
  4. PackageSession messenger protocol, embedded in a 2.3 MB obfuscated file
  5. ExfiltrateSession P2P network, which resembles encrypted chat traffic
  6. ExtraRepublishes itself with a stolen OIDC token; persists in .claude/ hooks and .vscode/tasks.json
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.
StageLiteLLM 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)
Triggercook.pth and sitecustomize.py run at every Python startupObfuscated 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
FingerprintHost and network info; checks for a once-only marker fileuname -a, /etc/hosts, OS detailsCI platform, OS, and runtime checks gate collection
Collect16 modules: cloud, SSH and GPG keys, npm and PyPI config, browser databases, AI tool auth files127 path patterns on Linux, 113 on macOS, plus the full environmentGitHub Actions secrets, AWS IMDS, Secrets Manager and SSM, Vault, Kubernetes tokens
PackageJSON encrypted with AES-256-CBC, key wrapped with RSA-4096In-memory tar, gzip, HMAC-signed chunksSession messenger protocol embedded in a 2.3 MB obfuscated file
ExfiltrateHTTP POST to a lookalike domain path named /healthcheckRoughly 29,400 DNS TXT queries per 500 KiB archiveSession P2P network, which looks like encrypted chat traffic
ExtraHourly remote Python loaderNone observedSelf-republishes via stolen OIDC tokens; persists in .claude/ and .vscode/

The exfiltration column is where detection usually fails. The LiteLLM 1.107.0 compromise posted its encrypted archive to a path named /healthcheck on a domain resembling the project's own. The node-ipc compromise 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.

TargetWhere it typically livesWhy attackers want it
Browser passwords and cookiesChromium Login Data and Cookies databases, Firefox logins.json and key4.dbA session cookie skips the password and the MFA prompt
Crypto walletsWallet files and browser extension storageDirect, irreversible theft
Package registry tokens~/.npmrc, ~/.pypirc, NPM_TOKENPublish rights over every package the victim maintains
Source control~/.git-credentials, GitHub CLI token, GITHUB_TOKEN in CIRepository 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/tokenCloud control plane access, often at the build role's privileges
SSH and signing keys~/.ssh/, GPG keyringsServer access and the ability to sign as the victim
Environment and dotfilesProcess environment, .env, .env.productionWhatever the application or pipeline was given
AI coding tools~/.claude/.credentials.json, ~/.codex/auth.json, ~/.gemini/oauth_creds.json, ~/.cursor/mcp.jsonBillable 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 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 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 visible to the current user. It never prints a value.

Bash
#!/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:

Plain 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 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 used a plain postinstall script in a new dependency, plain-crypto-js. The Miasma "Phantom Gyp" waves used a binding.gyp file, which makes npm run a native build even when package.json declares no script.

TriggerHow it runsExampleBlocked by default in npm v12?
preinstall / install / postinstallnpm runs the script during installThe axios compromise dropper in plain-crypto-jsYes
Implicit node-gyp rebuildA binding.gyp file triggers a native build with no script in package.jsonThe Miasma "Phantom Gyp" wavesYes
Git dependency preparenpm runs prepare when installing from git@tanstack/setup, injected as an optional git dependencyYes, git dependencies need --allow-git
require() or importTop-level module code runs on loadnode-ipc's appended .cjs codeNo
Python startup hookssite executes import lines in .pth files and imports sitecustomizeLiteLLM's one-line cook.pthNot applicable; runs on every interpreter start

Default-on install scripts were a design mistake, and the ecosystem has started to correct it. pnpm 10 blocked dependency lifecycle scripts by default in January 2025. npm v12 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:

Plain 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 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:

Bash
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, the first known self-propagating npm worm, made that loop real in September 2025. The @ctrl/tinycolor compromise 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 workflow pulled an OIDC token from the runner and published through the project's 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 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.

  1. 2026MayAgent Platform master key committed to a public repo, never revokeda GitHub organization owner's token
  2. about five months
  3. Oct 1022:11Throwaway branch runs cache.yml, posts 6 org secrets to webhook.siteGH_TOKEN, GitHub App key, 3 API keys
  4. Oct 1212:16Second workflow binds 5 environments, takes 14 new secretsmodel API keys, Postgres and Redis passwords, Playwright session cookies
  5. nine days
  6. Oct 2110:51Release workflow builds container images from lookalike source Berri-AI/litellm
  7. 11:39litellm==1.107.0 wheels published to PyPIcode execution at every Python start on affected hosts
  8. 11:46Socket flags the wheels as malware
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.
When (UTC)EventWhat it gave the attacker
May 2026Agent Platform master key committed to a public repository and never revoked, per LiteLLM's investigationA GitHub organization owner's token
Oct 10, 22:11Throwaway branch runs .github/workflows/cache.yml, posting six org secrets to webhook.siteGH_TOKEN, a GitHub App ID and private key, three API keys
Oct 12, 12:16Second workflow binds five environments and takes 14 more secretsModel provider keys, database and Redis passwords, Playwright session cookies
Oct 21, 10:51 to 11:06Release workflow builds container images from the lookalike Berri-AI/litellm sourceImages carrying the infostealer, with build provenance attached
Oct 21, 11:39:51litellm==1.107.0 wheels published to PyPICode execution on every Python start in affected environments
Oct 21, 11:46:30Socket flags the wheels as malwareDetection, 6 minutes 39 seconds after publish

Our timeline analysis 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, 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. Microsoft found 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 served an infostealer to people searching for the tool after Node.js stopped bundling it.

DimensionDesktop infostealer (RedLine, Lumma)Package infostealer (Shai-Hulud, node-ipc, LiteLLM 1.107.0)
DeliveryPhishing, malvertising, cracked software, fake installersMalicious or compromised npm, PyPI, and other registry releases
TriggerThe user runs an executableInstall script, import, or interpreter startup, often in CI
PlatformMostly Windows, some macOSLinux, macOS, and Windows; CI runners and developer laptops
Main targetsBrowser passwords and cookies, walletsRegistry, cloud, CI, SSH, and AI tool credentials
Business modelMalware-as-a-service subscriptions; logs resold in bulkDirect use of access, worm propagation, resale, ransomware partnerships
Time to impactLogs sold and used over months or yearsRepublishing within minutes or hours
Primary defensePhishing-resistant MFA, short sessions, endpoint detectionPre-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). 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 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 refuses versions younger than a set age. pnpm 11 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 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.