Back to Intelligence

Shai-Hulud ChainDrop npm Worm + AWS Bedrock Token-Jacking: OTX Pulse Analysis — Credential Theft Detection Pack

SA
Security Arsenal Team
October 8, 2026
10 min read

Two fresh OTX pulses paint a converging picture: credentials are being stolen at scale from developer workstations and CI/CD pipelines via a self-propagating npm supply chain worm, and harvested cloud keys are being systematically validated — not just for compute abuse, but for access to Amazon Bedrock LLM infrastructure. This briefing breaks down both pulses and delivers a detection pack your SOC can deploy today.


Threat Summary

Pulse 1 — TensorLake npm SDK Compromise (Shai-Hulud / ChainDrop): Version 0.5.144 of the TensorLake npm SDK (~12,000 weekly downloads) was backdoored in a continuation of the Shai-Hulud worm campaign. The malicious release carries obfuscated JavaScript that executes during package install (a preinstall hook) and sweeps the host for credentials: npm tokens, GitHub PATs, AWS access keys, HashiCorp Vault tokens, Kubernetes kubeconfigs, SSH private keys, and AI development tool credentials. The "worm" designation is critical — Shai-Hulud variants self-replicate by using stolen npm tokens to publish poisoned versions of every package the victim maintainer controls, turning each compromise into dozens more.

Pulse 2 — AWS Keys Validated for Bedrock Access: Datadog Security Labs research (surfaced via OTX) documents what happens after key theft. Attackers run a two-stage validation pipeline: first sts:GetCallerIdentity to confirm the key is alive, then Bedrock-specific enumeration — ListFoundationModels and Converse/InvokeModel calls — to determine whether the credential can consume expensive LLM resources. Credential harvesting platforms (including one tracked as KMON_NOC) automate this triage at scale.

The connective tissue: Campaigns like Shai-Hulud are the supply side — industrial-scale harvesting of developer and cloud credentials. Platforms like KMON_NOC are the demand side — automated validation and monetization of those keys. LLM access is the emerging prize: Bedrock access enables model abuse, data extraction through inference endpoints, and attribution laundering behind victim AWS accounts. The 36 indicators in Pulse 2 include IPv4 addresses associated with credential validation infrastructure.


Threat Actor / Malware Profile

Shai-Hulud / ChainDrop (npm Worm)

  • Attribution: Unknown. Consistent with a financially motivated actor exploiting maintainer trust in the npm ecosystem.
  • Distribution method: Trojanized package versions published to the npm registry using stolen maintainer tokens. Execution triggers automatically via preinstall/postinstall lifecycle scripts — no victim interaction beyond npm install.
  • Payload behavior: Obfuscated JavaScript (typically base64-layered or hex-encoded blobs decoded at runtime) that walks predictable credential paths: ~/.npmrc, ~/.aws/credentials, ~/.config/gh/hosts.yml, ~/.kube/config, ~/.ssh/, ~/.vault-token, environment variables (AWS_ACCESS_KEY_ID, GITHUB_TOKEN, NPM_TOKEN), and AI tooling configs (Claude, OpenAI keys).
  • C2 communication: Exfiltration typically over HTTPS to attacker-controlled endpoints or webhook-style services (webhook.site, ngrok, or throwaway GitHub/GitLab repos used as dead drops).
  • Persistence & propagation: The defining feature — stolen npm tokens are used to publish malicious patch versions of every package the victim maintains. GitHub tokens may be used to push malicious workflow files or create repos. Persistence on the host itself is secondary; the worm lives in the supply chain.
  • Anti-analysis: Heavy obfuscation, environment checks, and execution confined to the install phase, which many sandbox pipelines skip.

AWS Credential Validation Tooling (KMON_NOC et al.)

  • Distribution method: These platforms consume credentials acquired elsewhere — infostealers, supply chain worms, leaked .env files, public GitHub commits.
  • Payload behavior: Scripted AWS API enumeration: GetCallerIdentity (cheap, quiet), iam:GetUser/ListAccessKeys, then Bedrock probes: ListFoundationModels, InvokeModel, Converse. Success means the key is marketable or immediately usable.
  • Detection surface: This activity lives in CloudTrail, not on the endpoint. Validation bursts from unfamiliar ASNs/IPs against your key material are the tell.

IOC Analysis

Pulse 1 — File hashes (2)

SHA256 hashes for the malicious TensorLake package payloads:

  • 25a0735d0db7dc40e5d45ce42d9c106067e6a66e184d967cfecfab17c3bcb5ef
  • b50a00900399ba99fb6ce1fc151519cb99d44320ef2a631f2237e1aea0ad6fec

Operationalization: These are dropped/executed artifacts. Push to EDR blocklists (hash-based prevention), cross-reference against package cache directories (node_modules, npm cache at ~/.npm/_cacache), and hunt in software inventory for tensorlake@0.5.144. Hash IOCs for npm packages decay fast — pair with behavioral detections below.

Pulse 2 — IPv4 + File hashes (36 total, sample below)

  • 112.78.151.90, 78.109.78.211, 83.194.172.248, 103.160.185.100, 109.146.93.39, 115.138.247.83
  • SHA256: 923641364ef0ce3a6f1d944890244082b8c7f29c9600c0433b2a0ca9822c0608, c9335bb8a21bd2c568d03b040fb86a0e72145691e54a33495ee0cfaac55835dc

Operationalization: The IPs are credential-validation and harvesting infrastructure. They belong in:

  1. Egress firewall/proxy block lists (prevents exfil to validation nodes)
  2. Threat intel feeds ingested into SIEM — correlate against AWS CloudTrail sourceIPAddress fields. If any of your IAM principals authenticated from these IPs, that key is compromised, full stop.
  3. Netflow retrospective — check for developer workstations or build agents talking to these hosts.

Tooling: Decode the obfuscated JavaScript with de4js, js-beautify, or manual Node.js AST analysis (safe in a container). Triage hashes in VirusTotal/MalwareBazaar. Pivot IPs in GreyNoise and AbuseIPDB to confirm validation-infra behavior (scanner vs. C2).


Detection Engineering

YAML
---
title: npm Lifecycle Script Spawning Credential Access Tools
translation_key: shai_hulud_npm_preinstall_cred_access
id: 7c3f9a21-4b8e-4d2a-9f1c-2e5b8a6d4c71
status: experimental
description: Detects node/npm processes spawned by package lifecycle scripts (preinstall/postinstall) accessing credential stores — consistent with Shai-Hulud/ChainDrop supply chain worm behavior (TensorLake compromise).
author: Security Arsenal Threat Intel
references:
    - https://socket.dev/blog/tensorlake-compromise
date: 2026/10/09
logsource:
    category: process_creation
    product: windows
    os: windows
detection:
    selection_parent:
        ParentCommandLine|contains:
            - 'preinstall'
            - 'postinstall'
            - 'npm install'
        ParentImage|endswith:
            - '\node.exe'
            - '\npm.cmd'
    selection_command:
        CommandLine|contains:
            - '.aws\credentials'
            - '.npmrc'
            - '.kube\config'
            - '.ssh\'
            - '.vault-token'
            - 'GITHUB_TOKEN'
            - 'AWS_ACCESS_KEY_ID'
            - 'id_rsa'
    condition: selection_parent and selection_command
falsepositives:
    - Legitimate build tooling reading config during CI builds
level: high
tags:
    - attack.credential_access
    - attack.t1552.001
    - attack.t1195.002
---
title: Suspicious AWS Credential Validation Burst From Unusual Source
id: 2d8e4b17-9c3a-4f6e-a1d5-8b7c3e9f2a64
status: experimental
description: Detects AWS GetCallerIdentity and Bedrock enumeration calls from non-corporate IP space — consistent with KMON_NOC-style credential validation platforms testing stolen keys for Bedrock access.
author: Security Arsenal Threat Intel
references:
    - https://securitylabs.datadoghq.com/articles/beyond-valid-credentials-how-exposed-aws-keys-are-tested-for-amazon-bedrock-access
date: 2026/10/09
logsource:
    product: aws
    service: cloudtrail
detection:
    selection_events:
        eventSource:
            - 'sts.amazonaws.com'
            - 'bedrock.amazonaws.com'
        eventName:
            - 'GetCallerIdentity'
            - 'ListFoundationModels'
            - 'InvokeModel'
            - 'Converse'
    filter_corporate:
        sourceIPAddress|cidr:
            - '10.0.0.0/8'
            - '172.16.0.0/12'
            - '192.168.0.0/16'
    filter_known_validation_ips:
        sourceIPAddress:
            - '112.78.151.90'
            - '78.109.78.211'
            - '83.194.172.248'
            - '103.160.185.100'
            - '109.146.93.39'
            - '115.138.247.83'
    condition: selection_events and not filter_corporate
falsepositives:
    - Developers testing from home connections (baseline expected source ASNs per principal)
level: critical
tags:
    - attack.discovery
    - attack.t1526
    - attack.t1078.004
---
title: Exfiltration of SSH Keys or Cloud Credentials to External Webhook
id: 5f1a8c43-2e7d-4b9a-c6e3-1d4f7a8b2e95
status: experimental
description: Detects curl/wget/Invoke-WebRequest transmitting credential files or cloud config content to external endpoints — a common exfil pattern in npm supply chain payloads.
author: Security Arsenal Threat Intel
date: 2026/10/09
logsource:
    category: process_creation
    product: windows
detection:
    selection_tool:
        Image|endswith:
            - '\curl.exe'
            - '\wget.exe'
            - '\powershell.exe'
    selection_exfil:
        CommandLine|contains:
            - 'id_rsa'
            - '.aws'
            - 'credentials'
            - '.npmrc'
            - 'kube/config'
            - '.vault-token'
    selection_net:
        CommandLine|contains:
            - 'http://'
            - 'https://'
            - '-d @'
            - '-F '
            - 'Invoke-WebRequest'
            - 'Invoke-RestMethod'
    condition: selection_tool and selection_exfil and selection_net
falsepositives:
    - Backup scripts (rarely reference raw credential files)
level: high
tags:
    - attack.exfiltration
    - attack.t1041
    - attack.t1552
KQL — Microsoft Sentinel / Defender
// Sentinel hunt: npm/node processes touching credential stores + AWS validation IPs
// MDE: DeviceProcessEvents — Shai-Hulud install-time credential harvesting
let CredPaths = dynamic([".aws/credentials", ".npmrc", ".kube/config", "id_rsa", ".vault-token", "hosts.yml"]);
let ValidationIPs = dynamic(["112.78.151.90", "78.109.78.211", "83.194.172.248", "103.160.185.100", "109.146.93.39", "115.138.247.83"]);
let InstallCredAccess =
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | where FileName in~ ("node.exe", "npm.cmd", "npm", "node", "cmd.exe", "powershell.exe", "sh", "bash")
    | where ProcessCommandLine has_any (CredPaths)
       or (InitiatingProcessCommandLine has_any ("preinstall", "postinstall", "npm install", "npm ci"))
    | project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, SHA256;
let ValidationContact =
    DeviceNetworkEvents
    | where TimeGenerated > ago(7d)
    | where RemoteIP in (ValidationIPs)
    | project TimeGenerated, DeviceName, InitiatingProcessFileName, RemoteIP, RemotePort;
union InstallCredAccess, ValidationContact
| sort by TimeGenerated desc
PowerShell
# IOC Hunt: Shai-Hulud npm worm + AWS credential validation infrastructure
# Run on developer workstations and build agents. Output: findings to console + CSV.
$findings = @()

# 1. Check for compromised TensorLake package version in node_modules trees
Write-Host "[*] Scanning for tensorlake@0.5.144..." -ForegroundColor Cyan
$npmRoots = @("$env:USERPROFILE", "$env:ProgramFiles", "C:\Projects", "C:\dev", "C:\src") | Where-Object { Test-Path $_ }
foreach ($root in $npmRoots) {
    Get-ChildItem -Path $root -Recurse -Filter "package.json" -ErrorAction SilentlyContinue -Depth 6 |
        Where-Object { $_.FullName -match "tensorlake" } |
        ForEach-Object {
            $content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
            if ($content -match '"version"\s*:\s*"0\.5\.144"') {
                $findings += [PSCustomObject]@{ Type="MaliciousPackage"; Path=$_.FullName; Detail="tensorlake 0.5.144 confirmed" }
            }
        }
}

# 2. Hash-hunt known malicious payloads in npm cache and node_modules
$malHashes = @(
    "25a0735d0db7dc40e5d45ce42d9c106067e6a66e184d967cfecfab17c3bcb5ef",
    "b50a00900399ba99fb6ce1fc151519cb99d44320ef2a631f2237e1aea0ad6fec",
    "923641364ef0ce3a6f1d944890244082b8c7f29c9600c0433b2a0ca9822c0608",
    "c9335bb8a21bd2c568d03b040fb86a0e72145691e54a33495ee0cfaac55835dc"
)
Write-Host "[*] Hash-hunting npm cache..." -ForegroundColor Cyan
$cacheDir = "$env:LOCALAPPDATA\npm-cache"
if (Test-Path $cacheDir) {
    Get-ChildItem $cacheDir -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
        $h = (Get-FileHash $_.FullName -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash
        if ($malHashes -contains $h.ToLower()) {
            $findings += [PSCustomObject]@{ Type="MaliciousHash"; Path=$_.FullName; Detail=$h }
        }
    }
}

# 3. Flag suspicious preinstall/postinstall hooks across installed packages
Write-Host "[*] Auditing install lifecycle scripts..." -ForegroundColor Cyan
foreach ($root in $npmRoots) {
    Get-ChildItem -Path $root -Recurse -Filter "package.json" -ErrorAction SilentlyContinue -Depth 6 |
        Where-Object { $_.FullName -match "node_modules" } |
        ForEach-Object {
            $c = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
            if ($c -match '"(preinstall|postinstall)"\s*:\s*"[^"]*(curl|wget|Invoke-|base64|eval|child_process|\.ssh|\.aws)') {
                $findings += [PSCustomObject]@{ Type="SuspiciousHook"; Path=$_.FullName; Detail="Lifecycle script with credential/exfil indicators" }
            }
        }
}

# 4. Check active/historical network connections to validation infrastructure
$badIPs = @("112.78.151.90","78.109.78.211","83.194.172.248","103.160.185.100","109.146.93.39","115.138.247.83")
Write-Host "[*] Checking connections to validation IPs..." -ForegroundColor Cyan
Get-NetTCPConnection -ErrorAction SilentlyContinue | Where-Object { $badIPs -contains $_.RemoteAddress } |
    ForEach-Object { $findings += [PSCustomObject]@{ Type="MaliciousConnection"; Path="PID $($_.OwningProcess)"; Detail=$_.RemoteAddress } }

# 5. Evidence of credential store access staging (recently modified credential files)
$credFiles = @("$env:USERPROFILE\.aws\credentials", "$env:USERPROFILE\.npmrc", "$env:USERPROFILE\.kube\config", "$env:USERPROFILE\.vault-token")
foreach ($f in $credFiles) {
    if (Test-Path $f) {
        $item = Get-Item $f
        if ($item.LastWriteTime -gt (Get-Date).AddDays(-7)) {
            $findings += [PSCustomObject]@{ Type="CredentialFileRecentChange"; Path=$f; Detail="Modified $($item.LastWriteTime)" }
        }
    }
}

$findings | Format-Table -AutoSize
$findings | Export-Csv -Path ".\shaihulud_hunt_$(Get-Date -Format 'yyyyMMdd_HHmm').csv" -NoTypeInformation
Write-Host "[*] Hunt complete. $($findings.Count) findings." -ForegroundColor Green

Response Priorities

Immediate (0–4 hours)

  • Block the eight validation IPs at egress and ingest all file hashes into EDR prevention.
  • Hunt for tensorlake@0.5.144 in software inventory, package-lock files, and npm caches across developer workstations, CI runners, and container base images.
  • Query CloudTrail for any IAM principal activity from the listed IPs — any hit means that key is burned.
  • Freeze npm publish workflows until maintainer accounts are verified clean.

24 Hours

  • Rotate credentials aggressively: any developer or build agent that installed the compromised package must rotate npm tokens, GitHub PATs, AWS keys, Vault tokens, kubeconfigs, and SSH keys — assume all were exfiltrated.
  • Audit npm for downstream publishes: check every package your org's maintainers control for versions published in the last 14 days you don't recognize (worm propagation).
  • Review Bedrock/CloudTrail InvokeModel and Converse usage for anomalies — unexpected model invocations or regions indicate key abuse already underway.
  • Enable Bedrock invocation logging if not already active.

1 Week

  • Architectural hardening: enforce short-lived credentials everywhere — OIDC-based npm publishing (trusted publishing) instead of long-lived tokens, IAM roles with instance profiles/IRSA instead of static AWS keys.
  • Deploy SCPs or permission boundaries denying Bedrock access to principals that don't need it — shrink the blast radius of any stolen key.
  • Pin dependencies with lockfile integrity checks and block lifecycle script execution in CI (npm ci --ignore-scripts) where feasible.
  • Add package-age and maintainer-change gating to your internal npm proxy (e.g., quarantine packages published < 72 hours ago).

Related Resources

Security Arsenal Incident Response Managed SOC & MDR Services AlertMonitor Threat Detection From The Dark Side Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.