Skip to content
← Blog

Technical explainer

Why your AWS keys keep ending up in package-stealer logs

3 min read

When a malicious npm or PyPI package runs on your laptop, it doesn't drop ransomware. Ransomware on a developer machine has terrible ROI for the attacker: small payment ceiling, alerts triggered immediately, no persistence.

What pays is credential theft. A single AWS root key from a developer machine can rack up hundreds of thousands of dollars in mining costs before the bill triggers an alert. A GitHub PAT can publish a malicious version of every package the developer maintains. An npm publish token can do the same. A Stripe key can drain a startup's revenue account.

The economics are clear. The patterns in the malware reflect those economics. Here's what to expect.

The file paths every credential-stealer hits

Researchers analyzing npm and PyPI malware over the last three years have found near-universal targeting of the same paths:

Cloud credentials

  • ~/.aws/credentials and ~/.aws/config
  • ~/.config/gcloud/application_default_credentials.json
  • ~/.azure/credentials
  • ~/.config/digitalocean/config.yaml

Source-control tokens

  • ~/.netrc (where git stores HTTPS auth)
  • ~/.config/gh/hosts.yml (GitHub CLI)
  • ~/.gitconfig
  • ~/.ssh/id_* (the private keys; some malware also tries ~/.ssh/known_hosts to know which servers to target next)

Package-publishing tokens

  • ~/.npmrc (npm publish auth — high-value: lets the attacker propagate)
  • ~/.pypirc (PyPI publish auth)
  • ~/.gem/credentials

Application secrets

  • .env, .env.local, .env.production in the current working directory and parents
  • Recursive scan up to home, sometimes grepping for known prefixes (AWS_, STRIPE_, SENTRY_DSN)

Browser credentials (rarer but increasing)

  • Chrome's Login Data SQLite file
  • Firefox's key4.db and logins.json
  • Recently: VS Code's auth cache (~/.config/Code/User/globalStorage/)

Crypto wallets

  • ~/.config/Solana/id.json
  • MetaMask local storage in browser profiles
  • Hardware wallet seeds where they leak

The two delivery patterns

Almost all of it ships one of two ways:

Pattern 1: postinstall script. package.json declares a postinstall hook that runs a shell command. The shell command reads the target files, gzips them, and POSTs to an attacker-controlled URL. Optionally also curl | bash for a stage-2 payload.

Pattern 2: import-time code. The package's main module runs the same logic when require() or import happens. This survives npm install --ignore-scripts, which is the standard mitigation for pattern 1.

A combined attack does both: postinstall for the developer machine, import-time for the CI/production deploy. Same payload, two delivery surfaces.

What stops them at different layers

At the install gate (best): the gate scores the package on more than twenty signals before download. Suspicious postinstall scripts get caught by the lifecycle-script analyzer (looks for paths like ~/.aws, ~/.ssh, ~/.npmrc, curl-pipe-bash patterns, exec of base64-decoded payloads). The package returns 403, the install fails, nothing ever ran.

At the OS sandbox (fallback): if a package somehow gets past the gate, the OS-level sandbox restricts what its install scripts can read and where they can write. On Linux, Landlock prevents reads of ~/.ssh and ~/.aws. On macOS, sandbox-exec does the same. On Windows, low-integrity prevents writes to user profile paths. The script runs but the sensitive files it wants to grab are invisible to it.

After the fact (worst): rotate every credential the malware could have touched. AWS keys, GitHub PATs, npm tokens, ssh keys, the lot. If you don't have a clear picture of which packages installed when, rotate everything.

What an active credential theft looks like in your logs

You usually don't see it. The whole point is to be quiet. Tells when you do see something:

  • Unusual outbound POST in your ~/.npm/_logs/*.log (some packages log their install activity here)
  • A new file in /tmp or /var/folders (macOS) right after an install
  • Unexpected network connection during npm install to a domain that isn't npmjs.org or a known CDN
  • A GitHub Action you didn't authorize pushing a tag to your repo (means your PAT leaked)
  • Unfamiliar IAM activity in your AWS console (means your AWS key leaked)

Most of these are too late. The credential is already exfiltrated. Rotation is the only remaining action.

The cheap defense that actually works

You can't audit every install script. You can't read every package's code. You can't pin every transitive dep. What you can do is install a check that runs every install, every time, and refuses packages whose install scripts touch credential paths.

veln onboarding
veln wrapper on

Twelve trust signals. Three credential-path patterns alone account for the majority of the recent malware corpus. The check costs you nothing once it's set up. The credential rotation, if you don't have the check, is a Saturday afternoon. Choose accordingly.