Back to Intelligence

AsyncAPI npm Supply-Chain Compromise: sync.js RAT via Lifecycle Hooks and IPFS Payload Delivery — OTX Detection Pack

SA
Security Arsenal Team
September 27, 2026
9 min read

Threat Summary

AlienVault OTX pulse reporting describes a July 2026 supply-chain intrusion against the official AsyncAPI npm organization in which unknown operators published trojanized versions of trusted packages under the AsyncAPI namespace. The campaign weaponized normal developer behavior — npm install, transitive dependency resolution, and CI/CD package restoration — to execute malware through obfuscated package lifecycle hooks such as preinstall, install, and postinstall. The staged payload, tracked in the pulse as sync.js, functioned as a multi-stage Remote Access Trojan delivery chain with Node.js targeting and IPFS-based payload retrieval.

The collective picture is a developer-first intrusion path rather than a broad phishing operation. The attacker objective is to convert package trust into execution on workstations, build agents, artifact runners, and cloud CI runners. Once a maintainer machine or pipeline is compromised, the likely follow-on value is theft of npm tokens, Git credentials, cloud secrets, signing keys, source code, and access to downstream publishing workflows. This creates risk far beyond the initially infected host because a single poisoned dependency can fan out through monorepos, container builds, preview deployments, and production release jobs.

Threat Actor / Malware Profile

The actor is unattributed in the pulse, but the tradecraft aligns with financially motivated and espionage-capable npm ecosystem intrusion patterns: abuse of a respected namespace, versioned malicious releases, lifecycle script execution, payload staging from censorship-resistant infrastructure, and deployment of a RAT for durable access. The named artifact family is sync.js, a JavaScript-oriented loader or RAT component embedded in the package install path rather than a conventional executable dropped to disk.

Distribution method: malicious AsyncAPI-scoped npm packages published to the public registry and pulled by developers, dependency bots, build servers, and CI/CD runners. The initial execution vector is package manager lifecycle logic, meaning the malicious JavaScript can run automatically during installation even if the package is never imported by application code.

Payload behavior: multi-stage Node.js execution, obfuscated lifecycle hooks, retrieval of second-stage content over IPFS gateways or IPFS-compatible HTTP endpoints, in-memory or fileless JavaScript evaluation, and deployment of RAT functionality for command execution, reconnaissance, credential theft, and additional payload staging. Expect attempts to read npmrc files, Git configuration, SSH keys, cloud SDK credential caches, browser credential stores where accessible, environment variables, and CI secret material exposed to build jobs.

C2 communication: the pulse explicitly identifies IPFS payload delivery. Treat outbound HTTP(S) to public IPFS gateways, /ipfs/ content-addressed paths, pinning services, and unusual user-agent strings from node, npm, yarn, pnpm, bash, curl, PowerShell, or build-agent processes as suspicious. After staging, RAT traffic may blend into HTTPS using cloud-hosted domains, webhook endpoints, or content-addressed retrieval rather than static C2 domains.

Persistence mechanism: primary persistence is ecosystem-level rather than host-only: malicious versions remain reproducible through lockfiles, cached tarballs, private registries, and CI caches. Host-level persistence may follow through scheduled tasks, startup folder entries, run keys, shell profile modification, malicious postinstall survivors in node_modules, or poisoned build scripts that re-execute on every restore.

Anti-analysis techniques: obfuscated JavaScript, lifecycle-triggered execution that appears benign in package managers, use of legitimate Node.js runtime, content-addressed IPFS staging to frustrate domain blocking, versioned package churn that invalidates hashes, and execution inside ephemeral CI containers that disappear after the job completes.

IOC Analysis

The pulse contains nine file-hash indicators only: three SHA256 values, four SHA1 values, and two MD5 values. No IPs, domains, URLs, or package-version identifiers were included in the indicator sample. That makes the IOC set useful for confirmation and retrospective matching, but insufficient as the primary defense because npm supply-chain payloads are frequently repacked, padded, minified, or rebuilt per release.

SOC teams should operationalize the hashes in four ways. First, load SHA256 into EDR, NDR, email, web proxy, and malware detonation blocklists; use SHA1 and MD5 only for secondary matching because of collision and lower-fidelity concerns. Second, sweep package caches and artifact stores: npm cache, yarn cache, pnpm store, node_modules, CI workspace snapshots, Docker layers, artifact repositories, and release bundles. Third, enrich with behavior: package lifecycle hooks spawning shells, node.exe making IPFS gateway requests, lockfile diffs introducing AsyncAPI-scoped dependencies outside change control, and build jobs contacting egress endpoints not required for the build. Fourth, decode and detonate suspicious package tarballs in an isolated sandbox using npm pack, npm view, tar extraction, deobfuscation with js-beautify or similar tooling, and dynamic analysis with lifecycle scripts enabled only in a throwaway environment.

Because hashes are brittle, the durable detection is the behavior chain: trusted namespace -> unexpected dependency/version drift -> lifecycle hook execution -> Node or shell child process -> IPFS or low-reputation HTTPS retrieval -> credential/token access -> outbound beaconing or second-stage execution.

Detection Engineering

YAML
---
title: 'AsyncAPI npm Lifecycle Hook Execution - Node or Shell Spawning'
id: '7f2a7c46-9f0c-4c0d-9d2a-4f5f0c2a9d11'
status: experimental
description: 'Detects npm, yarn, or pnpm install flows spawning script interpreters consistent with malicious package lifecycle hooks described in the AsyncAPI supply-chain pulse.'
author: 'Security Arsenal'
date: 2026/09/27
logsource:
  product: windows
  category: process_creation
level: high
tags:
  - attack.t1195.002
  - attack.t1059.007
  - attack.t1027
selection_parent:
  ParentImage|endswith:
    - '\npm.exe'
    - '\npm.cmd'
    - '\node.exe'
    - '\yarn.cmd'
    - '\pnpm.cmd'
    - '\bash.exe'
    - '\sh.exe'
selection_child:
  Image|endswith:
    - '\powershell.exe'
    - '\pwsh.exe'
    - '\cmd.exe'
    - '\node.exe'
    - '\curl.exe'
    - '\rundll32.exe'
    - '\regsvr32.exe'
    - '\wscript.exe'
    - '\cscript.exe'
selection_cli:
  CommandLine|contains:
    - 'preinstall'
    - 'postinstall'
    - ' install '
    - 'npm install'
    - 'npm ci'
    - 'node_modules'
    - 'sync.js'
condition: selection_parent and selection_child and selection_cli
falsepositives:
  - 'Legitimate packages with install scripts in controlled development environments'
---
title: 'Node Package Manager to Public IPFS Gateway Retrieval'
id: 'b9b0b6f4-7e48-4d19-9f0e-2f2f9a0b6f31'
status: experimental
description: 'Flags package manager or Node.js processes retrieving content from public IPFS gateway paths, matching the pulse description of IPFS payload delivery.'
author: 'Security Arsenal'
date: 2026/09/27
logsource:
  product: windows
  category: network_connection
level: high
tags:
  - attack.t1105
  - attack.t1071.001
  - attack.t1608.004
selection_process:
  Image|endswith:
    - '\node.exe'
    - '\npm.exe'
    - '\npm.cmd'
    - '\yarn.cmd'
    - '\pnpm.cmd'
    - '\curl.exe'
    - '\powershell.exe'
    - '\pwsh.exe'
    - '\bash.exe'
    - '\sh.exe'
selection_ipfs:
  DestinationHostname|contains:
    - 'ipfs.io'
    - 'gateway.ipfs.io'
    - 'cloudflare-ipfs.com'
    - 'dweb.link'
    - 'gateway.pinata.cloud'
    - 'ipfs.infura.io'
  DestinationPort:
    - 80
    - 443
    - 8080
condition: selection_process and selection_ipfs
falsepositives:
  - 'Approved decentralized application development or sanctioned IPFS tooling'
---
title: 'Node.js Credential or CI Secret Access After Package Restore'
id: 'c8a4d7f8-1f36-4f3b-9a2f-9a9e7b1c0d52'
status: experimental
description: 'Detects node or package-manager processes accessing credential, token, SSH, cloud, or CI secret material after dependency restoration activity.'
author: 'Security Arsenal'
date: 2026/09/27
logsource:
  product: windows
  category: file_event
level: critical
tags:
  - attack.t1552.001
  - attack.t1552.004
  - attack.t1078
selection_process:
  Image|endswith:
    - '\node.exe'
    - '\npm.exe'
    - '\npm.cmd'
    - '\yarn.cmd'
    - '\pnpm.cmd'
selection_target:
  TargetFilename|contains:
    - '\.npmrc'
    - '\.git-credentials'
    - '\.ssh\'
    - '\.aws\credentials'
    - '\.azure\'
    - '\.config\gcloud\'
    - 'GITHUB_TOKEN'
    - 'NPM_TOKEN'
    - 'id_rsa'
    - 'known_hosts'
condition: selection_process and selection_target
falsepositives:
  - 'Developer authentication flows and legitimate package publishing automation'
KQL — Microsoft Sentinel / Defender
let sha256 = dynamic(['bfaeb987faa6de2b5a5eb63b1233d055215b09b0349a9394f2175fd7cdf385e4','b9993a8ad0518849416798cf29668256ccb96598fc4423501ccab5312812653a','24b9ee242f21a73b55f7bb3297eafb33c60840907386b542ed79fc6b72365168']);
let sha1 = dynamic(['22bf76fe317ea6769bd38619bd440e42d119bd6b','c8cb3f6d5b90c46686d2bf531dc1a5786e27edc5','93d8cffab1171a115228808e526d9bd7fe935e4e']);
let md5 = dynamic(['d602f4eeb914cf32782799376a8c5953','d8a6b102c1715bd80393ce510931b1f6']);
let ipfs = dynamic(['ipfs.io','gateway.ipfs.io','cloudflare-ipfs.com','dweb.link','gateway.pinata.cloud','ipfs.infura.io','/ipfs/']);
let proc =
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where InitiatingProcessFileName has_any ('npm','npm.cmd','node','yarn','pnpm','bash','sh','powershell','pwsh','cmd') or FileName has_any ('node','npm','npm.cmd','yarn','pnpm','powershell','pwsh','cmd','curl')
| where ProcessCommandLine has_any ('preinstall','postinstall','npm install','npm ci','node_modules','sync.js','/ipfs/','ipfs.io')
| project TimeGenerated, DeviceName, AccountName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, SHA256, SHA1, MD5, FolderPath, ReportId;
let net =
DeviceNetworkEvents
| where TimeGenerated > ago(14d)
| where InitiatingProcessFileName has_any ('node','npm','npm.cmd','yarn','pnpm','curl','powershell','pwsh','bash','sh')
| where RemoteUrl has_any (ipfs) or RemoteIPType == 'Public'
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteUrl, RemoteIP, RemotePort, LocalIP, LocalPort, ReportId;
let files =
DeviceFileEvents
| where TimeGenerated > ago(14d)
| where SHA256 in (sha256) or SHA1 in (sha1) or MD5 in (md5) or FileName =~ 'sync.js' or FolderPath has 'node_modules'
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, FolderPath, SHA256, SHA1, MD5, ReportId;
union proc, net, files
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Events=count(), Artifacts=make_set(strcat(FileName, InitiatingProcessFileName, RemoteUrl), 20) by DeviceName, AccountName, SHA256, SHA1, MD5, RemoteIP, RemoteUrl
| order by LastSeen desc;
PowerShell
$iocs = @('bfaeb987faa6de2b5a5eb63b1233d055215b09b0349a9394f2175fd7cdf385e4','b9993a8ad0518849416798cf29668256ccb96598fc4423501ccab5312812653a','24b9ee242f21a73b55f7bb3297eafb33c60840907386b542ed79fc6b72365168','22bf76fe317ea6769bd38619bd440e42d119bd6b','c8cb3f6d5b90c46686d2bf531dc1a5786e27edc5','93d8cffab1171a115228808e526d9bd7fe935e4e','d602f4eeb914cf32782799376a8c5953','d8a6b102c1715bd80393ce510931b1f6');
$roots = @((Join-Path $env:USERPROFILE 'node_modules'),(Join-Path $env:USERPROFILE 'AppData\Local\npm-cache'),(Join-Path $env:USERPROFILE 'AppData\Local\pnpm\store'),(Join-Path $env:ProgramFiles 'nodejs'),'C:\builds','C:\actions-runner','C:\agent\_work') | Where-Object { Test-Path $_ };
foreach ($root in $roots) { Get-ChildItem -Path $root -Recurse -Force -ErrorAction SilentlyContinue -Include 'sync.js','package.json','package-lock.json','npm-shrinkwrap.json','pnpm-lock.yaml','yarn.lock' | ForEach-Object { $h = Get-FileHash -Path $_.FullName -Algorithm SHA256 -ErrorAction SilentlyContinue; if ($h -and ($iocs -contains $h.Hash.ToLower())) { Write-Output ('HASH_HIT ' + $_.FullName + ' ' + $h.Hash) }; if ($_.Name -eq 'package.json') { $txt = Get-Content -Path $_.FullName -Raw -ErrorAction SilentlyContinue; if ($txt -match 'preinstall|postinstall|sync.js|ipfs') { Write-Output ('SCRIPT_HIT ' + $_.FullName) } } } };
Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue | Where-Object { $_.RemotePort -in 80,443,8080 } | ForEach-Object { $p = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue; if ($p -and ($p.ProcessName -match 'node|npm|yarn|pnpm|powershell|pwsh|curl|bash')) { Write-Output ('NET_HIT ' + $p.ProcessName + ' pid=' + $_.OwningProcess + ' remote=' + $_.RemoteAddress + ':' + $_.RemotePort) } };
Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskPath -notlike '\Microsoft*' } | ForEach-Object { $actions = ($_.Actions | Out-String); if ($actions -match 'node|npm|sync.js|powershell|curl') { Write-Output ('TASK_HIT ' + $_.TaskName + ' ' + $_.TaskPath + ' ' + $actions) } };
Write-Output 'Sweep complete. Validate hits before containment; package caches and CI workspaces may contain legitimate copies of similarly named files.';

Response Priorities

  • Immediate: import the nine hashes into EDR and proxy blocklists, isolate endpoints or build agents with hash or sync.js hits, freeze npm publish and release jobs, disable lifecycle scripts during emergency package installation with npm config set ignore-scripts true, audit package-lock.json, yarn.lock, pnpm-lock.yaml, npm-shrinkwrap.json, npm cache, pnpm store, container layers, and artifact repositories for unauthorized AsyncAPI-scoped versions, and deny egress to public IPFS gateways unless explicitly approved.
  • 24h: treat exposed npm tokens, GitHub/GitLab tokens, SSH keys, cloud credentials, CI secrets, signing keys, and environment variables as potentially compromised; revoke and rotate them, force SSO reauthentication for maintainers, review npm audit logs and package publish events, inspect GitHub Actions, Azure DevOps, GitLab CI, Jenkins, CircleCI, and runner logs for unusual npm ci or npm install steps, and search for token use from impossible geography or autonomous system numbers.
  • 1 week: enforce private registry proxying with namespace allowlisting, require lockfile review and provenance or sigstore verification for security-critical packages, pin dependencies by integrity hash, run CI in ephemeral least-privilege containers without persistent secrets, block lifecycle scripts by default with explicit exceptions, alert on node_modules changes outside source control, segment build networks from production secrets, and add detections for package-manager processes spawning shells, reading credential paths, or contacting IPFS/content-addressed endpoints.

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.