Last week, the full picture emerged on a supply chain attack that should rattle anyone running a software team. North Korean state actors — the group tracked as UNC1069, linked to Lazarus — compromised the Axios NPM package. Not a fringe library. Axios has over 100 million weekly downloads. It runs inside virtually every JavaScript project. If your team builds software that touches Node.js, there's a very high probability Axios is somewhere in your stack.
The attack was live for three hours on March 31. In that window, enough damage was done that OpenAI had to revoke and rotate the code-signing certificates for its macOS applications. The full technical details and attribution to UNC1069 came out of Google Mandiant and Tenable this week.
Here's what happened, why it worked, and the specific configuration mistake — probably present in your pipelines right now — that turned a three-hour NPM compromise into a supply chain breach reaching into code-signing infrastructure.
How They Got In
This wasn't a credential-stuffing attack or a brute-forced password. It was human.
The attackers targeted Jason Saayman, the lead maintainer of the Axios NPM package. They invited him to a Slack workspace, scheduled a meeting on Microsoft Teams, and when the call started they showed a technically convincing error message. Then came the prompt to install what looked like a meeting app update. He installed it. That installed a remote access trojan.
They ran the same playbook on other high-profile Node.js maintainers. One target was invited to participate in a podcast recording and told to join via a fake Streamyard interface — same error message, same fake native app, same RAT.
This is a pattern that security researchers have been watching escalate for two years. North Korean actors have been specifically recruiting high-value targets in technical communities through fake job offers, podcasts, and conferences. The goal is access to package maintainer accounts — accounts that, with a single publish command, can push malicious code to every downstream user of a library.
With Saayman's npm credentials, the attackers published two trojanized versions: axios 1.14.1 and 0.30.4. Both were live on the npm registry from 00:21 to 03:20 UTC on March 31. Just over three hours.
What the Malicious Versions Did
Both versions added a dependency called plain-crypto-js — not a real package, just a name that looks like it belongs. When npm installed the compromised axios version, plain-crypto-js's postinstall hook executed an obfuscated JavaScript file called setup.js. The obfuscation used a two-layer scheme: reversed Base64 plus XOR cipher, designed to evade basic static analysis.
The payload was WAVESHAPER.V2 — a cross-platform backdoor that runs on Windows, macOS, and Linux. Once deployed, it established persistence and began beaconing to attacker-controlled C2 infrastructure.
If your npm install happened between those three hours and you pulled 1.14.1 or 0.30.4, you got the backdoor. The malicious versions have been removed from the registry, but any machines that installed during that window should be treated as compromised until proven otherwise.
How It Reached OpenAI's Signing Pipeline
The three-hour window was short enough that most teams didn't directly install the poisoned versions. But Axios hit OpenAI's systems anyway — through GitHub Actions.
OpenAI's workflow for signing macOS applications (for ChatGPT Desktop, Codex, and Codex-cli) used Axios as a dependency. The workflow specified Axios using a floating tag — a version reference that resolves to "whatever is current at execution time" rather than locking to a specific package version or commit hash.
This is standard practice. Ask any developer to audit their GitHub Actions workflows and you'll find floating references everywhere. uses: actions/checkout@v3. pip install requests. npm install axios@latest. Nobody pins. It's friction. It slows things down. The benefits aren't visible until something goes wrong.
When OpenAI's signing workflow ran on March 31, it pulled the latest axios — which at that moment was the compromised 1.14.1. The workflow had access to the certificate and notarization material used to sign macOS applications. Whether the private key was actually exfiltrated is uncertain — OpenAI says the timing and sequencing suggest it probably wasn't — but the exposure was real enough that they revoked the certificate anyway and are requiring all macOS users to update by May 8.
That's an expensive response to a misconfiguration that costs nothing to fix.
The Fix That Takes Twenty Minutes
Check if you installed the compromised versions. If any machine on your team ran an npm install between March 31 00:21 and 03:20 UTC and pulled axios:
npm list axios
If you see 1.14.1 or 0.30.4, treat that machine as compromised. Look for the persistence payload:
# macOS / Linux
ls -la ~/.config/waveshaper/
ps aux | grep setup.js
Check your npm caches as well. node_modules is not the only place this lands.
Pin your GitHub Actions to commit hashes. This is the most impactful change you can make today. Instead of:
uses: actions/checkout@v3
Use:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
That hash points to a specific commit. An attacker who compromises the v3 tag doesn't affect you. Tools like Mend Renovate and Dependabot can manage this automatically and give you PRs when pinned versions need updating.
Set a minimumReleaseAge on your package manager. This is the mitigation OpenAI identified as missing. Renovate supports it natively:
{
"packageRules": [
{
"matchManagers": ["npm"],
"minimumReleaseAge": "3 days"
}
]
}
Three days of age means a package published and immediately pulled — like the compromised axios versions — never touches your CI. The three-hour attack window simply doesn't reach you.
Scope what your CI workflows can access. If a workflow doesn't need signing certificates, don't give it signing certificates. Principle of least privilege in GitHub Actions means adding permissions: blocks explicitly and not letting workflows inherit broad permissions by default.
The Pattern Worth Watching
The LiteLLM supply chain attack we wrote about earlier this month involved attackers publishing malicious packages directly to PyPI. The Axios attack is different — it required social engineering a trusted human first. That's a harder attack to pull off, but it's also harder to detect and defend against.
When attackers compromise a maintainer account, the malicious version carries legitimate package provenance. It comes from the real author's npm account. It passes most automated checks. By the time anyone notices, the window is open and packages have been installed.
UNC1069 has been specifically targeting high-value open source maintainers because the ROI is enormous. Compromise one person with a widely-used package and you get inside the build pipelines of thousands of organizations simultaneously. No phishing your users directly. No breaking your authentication. Just one confused maintainer who installed a fake Teams update.
This is going to keep happening. The npm and PyPI ecosystems have no robust mechanism to prevent a legitimate account holder from publishing malicious code. The defenses all have to live on the consumer side: pinning, release age minimums, least-privilege CI, and auditing what you actually have installed.
For a small team building software, none of this is complex. It's an afternoon of work. But most teams aren't doing it, which is why an attack that was live for three hours is still generating incident reports two weeks later.
If your team is building software and you're not sure whether your pipelines are exposed to this class of attack, that's a conversation worth having before the next one. Get in touch.