ProBackend
supply chain attacks
1 hour ago10 min read

Five malicious versions of AsyncAPI packages were published to the Node Package Manager (npm) in a supply-chain attack that...

Five malicious packages in the AsyncAPI namespace were published to npm after attackers compromised GitHub Actions workflows, delivering a multi-stage malware payload designed to harvest credentials and developer secrets.

Five malicious versions of AsyncAPI packages were published to the Node Package Manager (npm)

Let me be blunt: this isn’t just another supply-chain breach. It’s the moment we stopped pretending that provenance equals safety.

On July 14, someone slipped five poisoned packages into npm under the AsyncAPI namespace. Not by stealing a token. Not by bribing a maintainer. They didn’t need to. They just waited for a misconfigured GitHub Actions workflow to do the dirty work for them. And when it did, npm’s own trusted-publisher system stamped those packages with SLSA attestations — the digital equivalent of a notary seal on a forged will.

The result? 2.25 million weekly downloads. Across five packages. One of them, @asyncapi/specs, was downloaded over two million times a week. That’s not a niche tool. That’s a foundational dependency in thousands of enterprise CI/CD pipelines. And now? It’s a backdoor.

I’ve seen a lot of supply-chain attacks. But this one? It’s elegant. Cruel. And terrifyingly simple.

The attacker didn’t write new code. They didn’t reverse-engineer the build. They just pushed a commit under a fake git identity — a ghost in the machine — and let the repository’s own release pipeline, configured to auto-publish on tag, do the rest. The packages were signed. Verified. Trusted. And installed by developers who thought they were safe because the system said so.

It’s like letting a stranger into your house because they’re wearing your neighbor’s keychain.

And the payload? It’s not some script kiddie’s toy. It’s a 92,000-line modular framework. Three stages. First, an obfuscated JavaScript snippet that triggers when you import the package. Then, a downloader that pulls the real payload from IPFS — decentralized, untraceable, and impossible to block without breaking the web. Finally, the beast: a full-blown credential harvester that talks to C2 servers over HTTP, Nostr relays, Ethereum smart contracts, and even a libp2p mesh network. That’s not just persistence. That’s resilience.

It’s designed to steal everything: CI/CD secrets, AI training tokens, browser passwords, cryptocurrency wallets, database credentials. It even drops Gitleaks and HackBrowserData like it’s handing out candy. But here’s the twist — Aikido says the harvesting functions don’t actually work. The code exits before collecting anything. So why bother?

Because shell access is all you need.

Once the malware’s in, the attacker doesn’t need automated exfiltration. They just log in manually. They don’t need to steal your keys. They just need to sit there and type them in themselves.

And if you’re in Russia? The malware shuts itself down. Ox Security confirmed it. No geopolitical alignment. No state sponsorship. Just a quiet, professional decision: avoid detection in a region where forensic scrutiny is… intense.

The exposure window? Four hours and seven minutes. Between 07:10 and 11:18 UTC on July 14. That’s it. One coffee break. One automated deploy. One moment of oversight.

And now? The packages are gone from npm. But your lock file? Your node_modules? Your CI runner? They’re still infected.

This isn’t a patch. It’s a housecleaning.

Pin your dependencies. Regenerate your lock files. Hunt down NodeJS/sync.js. Kill every process that doesn’t belong. Rotate every credential you’ve ever used in a pipeline. And then? Look at your CI/CD config again. And again. And again.

Because the next time, they won’t need a misconfiguration. They’ll just wait for you to click "Merge".

We’ve spent years building trust into our pipelines. We thought the system would protect us.

It didn’t.

Now we have to protect ourselves.

Five malicious versions of AsyncAPI packages were published to the

Five malicious versions of AsyncAPI packages were published to the

How the attack bypassed npm’s trusted-publisher system

Here’s the part that still makes me sick: the attacker didn’t break npm. They didn’t break GitHub. They didn’t even break the tooling.

They used the system exactly as designed.

npm’s trusted-publisher feature, built on GitHub OIDC, was meant to stop malicious maintainers from hijacking packages. It says: "If you’re pushing from a verified GitHub repo, we’ll trust you." And for good reason — it worked. For years.

But this attack? It turned that trust into a weapon.

The AsyncAPI repositories had a GitHub Actions workflow that automatically published new versions to npm whenever a git tag was pushed. It was clean. Automated. Efficient. And utterly blind to who pushed the tag.

The attacker didn’t need access to the repo. They didn’t need write permissions. They just needed to fork it — which anyone can do — and push a commit with a fake identity. No credentials. No 2FA. No audit trail. Just a commit message that looked like a normal release.

Then they triggered the workflow by pushing a tag. The workflow ran. It saw the tag. It checked the GitHub OIDC token. It saw the repo was verified. It published.

And npm? It saw the SLSA provenance attestation — the cryptographic proof that the package came from the official repo — and said: "This is safe."

The system didn’t fail. The assumption did.

We assumed that if a package came from a trusted workflow, the actor behind it was trusted too.

We were wrong.

This isn’t a flaw in the tool. It’s a flaw in our thinking.

We built automation to remove human error. But we forgot that humans are still the ones writing the automation.

And one bad line of YAML — one missing condition on the tag — is all it takes.

The attacker didn’t need to be inside the repo. They just needed to know it was there.

And now? Every organization that uses trusted-publisher with GitHub Actions has the same vulnerability. If your CI/CD workflow publishes on tag without validating the committer, you’re already compromised.

This isn’t a one-off. It’s a pattern.

And the fix? It’s not technical. It’s cultural.

Stop trusting workflows. Start trusting people.

Require pull requests for releases. Enforce code reviews. Add human gates to automation. Make someone say "yes" before a package goes live.

Because the next attacker won’t even need a misconfiguration.

They’ll just wait for you to say "yes".

How the attack bypassed npm’s trusted-publisher system

How the attack bypassed npm’s trusted-publisher system

The malware’s three-stage architecture — and why it’s so hard to detect

Let’s talk about the payload. Not the headlines. Not the stats. The actual code.

It’s not a virus. It’s not ransomware. It’s a system.

Stage one: obfuscated JavaScript. Just a single line, buried in a package that’s supposed to generate API docs. It looks like noise. Like a build artifact. Like something you’d ignore because "it’s just a dependency." But when you import it? It triggers a downloader. No alerts. No spikes. Just a quiet HTTP call to a random IPFS node.

Stage two: the IPFS fetch. IPFS isn’t a server. It’s a peer-to-peer network. You can’t block it without breaking the decentralized web. You can’t scan it without breaking privacy. And you can’t trace it without a global map of every node — which doesn’t exist.

The payload here? A JSON config file. It tells the malware where to go next. What to steal. How to communicate. And it’s signed — not with a key, but with a hash of the IPFS CID. The malware verifies it. Then it runs.

Stage three: the 92,000-line framework.

This isn’t a script. It’s an operating system inside your system.

It establishes persistence by modifying shell profiles. It hides in plain sight as a background Node.js process. It communicates over multiple channels — HTTP for C2, Nostr for decentralized messaging, Ethereum smart contracts for encrypted storage, and libp2p for mesh networking. That last one? It’s how Bitcoin nodes talk. It’s how Tor routes traffic. It’s designed to survive firewalls, proxies, and even air-gapped networks.

And here’s the kicker: it doesn’t just steal. It learns.

It scans your environment. Finds your CI/CD secrets. Maps your Kubernetes clusters. Identifies your AI model keys. It even checks for Docker sockets and AWS metadata endpoints. It’s not just a backdoor. It’s a reconnaissance bot.

And yet — Aikido says the harvesting functions don’t work. The code exits early. Why?

Because the attacker doesn’t need automation.

They just need a shell.

Once the malware’s in, they log in manually. They type the commands. They run the tools. They don’t need Gitleaks to find secrets. They just open the terminal and type "grep -r "token" ." themselves.

That’s why the malware is so hard to detect. It doesn’t behave like malware. It behaves like a developer.

It doesn’t exfiltrate data. It uses it.

And that’s the real danger.

EDR tools look for suspicious processes. But this isn’t suspicious. It’s just… familiar.

It’s a Node.js process. It’s using npm. It’s running in a CI runner. It’s doing what it’s supposed to do.

The only way to catch it? Behavioral analysis. Baseline your normal activity. And then watch for the one time it doesn’t.

Because when the attacker logs in? They’ll do something no developer would.

They’ll look for the wrong thing.

And that’s when you’ll know.

Miasma connection and the ghost in the code

The final payload? It looks like Miasma.

Not just similar. Identical. Same artifact names. Same config structure. Same hardcoded strings.

SafeDep researchers say it’s either a parallel build — same team, different toolchain — or a separate group that copied the Miasma brand after its source code leaked.

I don’t care which.

What matters is this: someone had a working, sophisticated backdoor. And they didn’t just use it. They shared it.

Miasma was never public. It was a private tool. Used by one group. One team. One operation.

And now? It’s in the wild.

That means one of two things:

Either the original operators got sloppy — and someone stole their code — or they wanted it out there.

The first scenario? Scary. It means even private malware can leak.

The second? Terrifying. It means someone’s turning cyber weapons into open-source projects.

This isn’t just an attack. It’s a signal.

We’ve been treating malware like it’s a secret. A weapon. A black box.

But what if it’s not?

What if the future of cyber warfare isn’t zero-days — it’s public repos?

What if the next big attack doesn’t come from a nation-state, but from a GitHub fork?

And what if the next Miasma isn’t hidden in a dark forum… but in npm?

The attacker didn’t just compromise AsyncAPI.

They compromised our assumption that malware is always hidden.

Now we know: it can be public. It can be trusted. And it can be downloaded by millions.

We built a world where code is shared freely.

We just forgot to ask: who’s sharing it?

And who’s watching?

Exposure window: 4 hours 7 minutes — and the systems still infected

The clock started at 07:10 UTC on July 14. It ended at 11:18.

Four hours and seven minutes.

That’s less time than it takes to finish a standup. Less than your lunch break.

In that window, 2.25 million downloads happened.

Not all of them were malicious. But all of them were vulnerable.

Because npm doesn’t roll back. It doesn’t delete history. It just hides the package.

And your lock file? Your node_modules? Your CI runner’s cache? They still have the bad code.

You think you’re safe because the package is gone? You’re not.

You’re infected.

And here’s the brutal truth: no one’s going to tell you.

Your CI/CD pipeline doesn’t alert you when a dependency changes. Your EDR doesn’t flag a Node.js process that’s been there for months. Your developer doesn’t know the difference between a legitimate package and a poisoned one.

They just see "npm install" and hit enter.

So what do you do?

First: purge. Delete every node_modules folder. Every lock file. Every cached build.

Second: pin. Don’t use ^ or ~. Pin every dependency to a specific version. Even the ones you didn’t knowingly install.

Third: audit. Run a dependency scan. Not just for known vulnerabilities — for unknown ones. Look for files named sync.js. Look for hidden Node.js processes. Look for IPFS connections in your network logs.

Fourth: rotate. Every credential. Every token. Every key. Every secret that touched that pipeline.

And fifth: assume you’re compromised.

Because you are.

This isn’t a one-time fix. It’s a reset.

You’re not just patching a package.

You’re rebuilding trust.

And that takes more than a script.

It takes time.

And humility.

Because the next time? The attacker won’t need four hours.

They’ll just wait for you to click "Deploy".

Mitigation: What to do now — and what no one’s telling you

Let’s cut through the noise.

Here’s what you’re supposed to do:

  • Pin your dependencies.
  • Regenerate lock files.
  • Remove NodeJS/sync.js.
  • Rotate credentials.

And here’s what no one’s telling you:

None of it matters if you don’t change how you think.

You can pin every package. But if your CI/CD pipeline still auto-publishes on tag? You’re still vulnerable.

You can rotate every credential. But if your EDR doesn’t detect manual shell access, you’re blind.

The real mitigation? Stop treating CI/CD as a black box. Add human review gates. Require two-person approval for releases. Audit your GitHub Actions workflows — especially those with on: push and tags triggers. And don’t forget: if you use 365 or other cloud platforms, ensure your CI/CD secrets are never hardcoded — use secrets management.

This attack didn’t exploit a bug. It exploited trust. And the only way to fix that is to rebuild your culture of security — not just your tooling.

More blogs