Back to glossary

Advanced Persistent Threat (APT)

An advanced persistent threat (APT) is a well-resourced, usually state-backed attacker that pursues a goal for months or years and adapts when blocked.

Also known as APT group, APT actor, advanced persistent threat actor, state-sponsored threat actor, nation-state hacker

An advanced persistent threat (APT), also called an APT group or APT actor, is a well-resourced, usually state-backed attacker that pursues one objective for months or years and adapts when defenders push back. In the open source supply chain, APTs rarely exploit bugs. They borrow trust, compromising a maintainer or publishing lookalike packages, and ship malware through legitimate registries.

The clearest recent case is the axios compromise. On March 31, 2026, attackers published two malicious axios versions to npm, and the exposure window lasted 2 hours and 53 minutes. Google and Microsoft each attributed the compromise to North Korea. Reaching that point took the attacker weeks of patient social engineering against one maintainer, and the malware itself was an ordinary install-time dropper. That ratio is the lesson. In package ecosystems, the "advanced" in advanced persistent threat is mostly patience applied to trust, and the attacker ends up holding a real account, real credentials, and sometimes a real pipeline. Our position: you cannot out-authenticate an APT that owns the maintainer's laptop. The defensible chokepoint in a supply chain attack is the artifact itself, analyzed for what it does between the moment it is published and the moment it is installed.

What is an advanced persistent threat?

#

An advanced persistent threat is defined by who is operating, not by what malware they use. The NIST definition, taken from SP 800-39 and reused across NIST's security control catalogs, describes an adversary that "pursues its objectives repeatedly over an extended period of time" and "is determined to maintain the level of interaction needed to execute its objectives."

Each word in the name does specific work:

  • Advanced means the operator can plan and run a multi-stage operation: reconnaissance, initial access, staging, command and control, and collection. The code is often unremarkable.
  • Persistent means the operation survives defender response. Takedowns produce new accounts, new infrastructure, and the next wave.
  • Threat means an organized team with tasking and a budget, not a single opportunist.

The term started narrower than it is today. Richard Bejtlich told a US congressional commission in 2012 that the US Air Force coined it in 2006 as an unclassified label for intrusion sets traced back to China. It now covers state-linked groups from many countries, each tracked under vendor names and numbers.

Hacking, cybercrime, and cyberwarfare are often used interchangeably with APT activity. The difference is tasking: who picks the target, and what happens when the defender wins a round.

Opportunistic cybercriminalAdvanced persistent threatCyberwarfare operation
Who picks the targetWhoever is exposed or installs the baitChosen in advance, then researchedChosen for strategic effect
Primary goalMoney, quicklyLong-term access, intelligence, or, for North Korea, revenueDisruption or destruction
After a takedownMoves on to a softer targetReturns with new personas and infrastructureWaits for the next opening or escalates
Typical open source formCredential-stealing typosquats published in bulkMaintainer takeover through fake job or partnership luresRare in package registries to date

One organization can do all three. We have tracked Lazarus Group publishing malicious npm packages aimed at developers. MITRE ATT&CK describes the same group as North Korean and state-sponsored, and ties it to the destructive 2014 Sony Pictures wiper attack in group entry G0032.

North Korea breaks the textbook split between espionage and money. The operators behind the axios compromise are tracked as a "financially motivated North Korea-nexus threat actor," in Google's words. Our research on the Contagious Interview campaign found one North Korean operation pursuing both cryptocurrency theft and strategic espionage, and we described it as "a state-directed, quota-driven operation with durable resourcing, not a weekend crew." The motive is often financial. The organization, patience, and persistence are an APT's.

Why do APTs target open source maintainers?

#

APTs target open source maintainers because one maintainer's credentials are a write path into thousands of other companies' builds. Start with how a registry normally works. npm authenticates a publisher (a logged-in user, a token, or an OIDC trusted publisher), accepts the tarball, and serves it to anyone whose version range matches. A project that declares "axios": "^1.14.0" receives 1.14.1 on its next fresh resolution, with no code change and no review. That design is deliberate: ranges let package managers deduplicate shared dependencies and ship fixes without coordinated releases.

The trust assumption sits in one place. The registry treats an authenticated publisher as a publisher acting in good faith, and nothing downstream re-checks intent. For an attacker with patience, that makes maintainer account takeover the cheapest route to scale. axios had about 100 million weekly downloads when it was compromised, by our count at the time.

The attack on axios did not exploit npm. It exploited a person. The lead maintainer, Jason Saayman, wrote in the post-mortem that the attacker reached his PC "through a targeted social engineering campaign and RAT malware." Asked how far that access went, he said the attacker had "enough access to hijack from my browser… lifting sessions or cookies." A remote access trojan (RAT) on a maintainer's machine inherits every session that machine has already authenticated.

The approach was not aimed at axios alone. Within days, maintainers of Lodash, dotenv, Fastify, and Node.js core reported being targeted by the same campaign: a fake company called "Openfort," a convincing Slack workspace, a call that kept getting rescheduled, and a prompt to install an "update" when the audio failed. Several of the people targeted were Socket engineers. Researchers connected the playbook to UNC1069, a cluster previously known for targeting cryptocurrency founders and investors one at a time. Pointing it at maintainers changes the math. One successful call can now reach every CI runner that resolves a popular package during the next few hours.

Anatomy of an APT supply chain operation: axios, March 2026

#

The axios operation had two clocks. The human operation ran for weeks. The technical window ran for less than three hours.

attacker actiondetection or responseattribution
Phase 1: the human operationweeks
  1. Mar 5, 2026A fake Openfort representative messages a Node.js core collaborator on LinkedIn, then moves him into private Slack workspaces.
  2. Mar 10–11More Slack invites from an account impersonating Openfort's co-founder reach other npm maintainers.
  3. ~Mar 17The axios lead maintainer installs a fake "update" during a staged call. A RAT now holds his browser sessions.
Phase 2: the publish windowMar 30–31, UTC
  1. Mar 30 05:57:32plain-crypto-js@4.2.0: a clean copy of crypto-js, published to build history.
  2. Mar 30 23:59:12plain-crypto-js@4.2.1 adds a postinstall dropper.
  3. Mar 31 00:05:41Socket flags 4.2.1 as malware, 16 minutes before anything depends on it.
  4. 00:21:58axios@1.14.1 published with the new dependency.
  5. 01:00:57axios@0.30.4 published. Both release lines are now poisoned.
  6. 03:15npm removes both axios versions. Exposure window: 2 h 53 min.
00:00 UTC04:00 UTC
Phase 3: names and repetitiondays to weeks
  1. Mar 31Google attributes the compromise to UNC1069, a North Korea-nexus actor.
  2. Apr 1Microsoft attributes the same compromise to Sapphire Sleet.
  3. Jun 17Microsoft ties Sapphire Sleet to a second npm compromise: 141 @mastra/* packages carrying the same staged-dependency pattern.
Sources: Socket research, the axios post-mortem, npm registry timestamps, Google Threat Intelligence Group, Microsoft Threat Intelligence.

The timeline above in text form, all times UTC:

Time (UTC)EventSource
Mar 5, 2026A fake Openfort representative contacts Node.js core collaborator Jean Burellier on LinkedIn, then invites him to private Slack workspacesSocket reporting
Mar 10–11Slack invites from an account impersonating Openfort's co-founder reach other npm maintainersSocket reporting
About Mar 17The axios lead maintainer's PC is compromised through social engineering and RAT malware, about two weeks before the publishaxios post-mortem
Mar 30, 05:57:32plain-crypto-js@4.2.0 published: a clean copy of crypto-js with no payloadnpm registry
Mar 30, 23:59:12plain-crypto-js@4.2.1 published with a postinstall droppernpm registry
Mar 31, 00:05:41Socket's automated detection flags plain-crypto-js@4.2.1 as malwareSocket reporting
Mar 31, 00:21:58axios@1.14.1 published with plain-crypto-js added as a dependencynpm registry
Mar 31, 01:00:57axios@0.30.4 published with the same dependencynpm registry
Mar 31, 01:38An axios collaborator opens a pull request to deprecate the versions and contacts npmaxios post-mortem
Mar 31, 03:15npm removes both malicious axios versions; exposure window 2 h 53 minaxios post-mortem
Mar 31Google Threat Intelligence Group attributes the compromise to UNC1069Google
Apr 1Microsoft attributes the compromise to Sapphire SleetMicrosoft
Jun 17141 @mastra/* packages published with an injected easy-day-js dependency; Microsoft later attributes it to Sapphire SleetSocket reporting; Microsoft

Three details in that sequence are the signature of an organized operator rather than an opportunist. First, the malicious dependency was staged with a clean version 18 hours before it was weaponized, so it would not look brand new. Second, both release lines were poisoned within 39 minutes, catching projects on ^0.30 ranges as well as ^1.14. Third, the dropper cleaned up after itself: it ran from a postinstall script, fetched a platform-specific payload, then deleted setup.js and swapped in a clean package.json so node_modules looked normal afterward.

The registry still remembers the window. npm removes unpublished versions but keeps their publish timestamps in the package document's time map. This query lists every axios version that has a timestamp but no longer exists:

Bash
$ curl -s https://registry.npmjs.org/axios | jq -r '. as $d | .time | to_entries[]
    | select(.key != "created" and .key != "modified")
    | select($d.versions[.key] == null) | "\(.value)  \(.key)"'
2026-03-31T00:21:58.168Z  1.14.1
2026-03-31T01:00:57.285Z  0.30.4

As of October 2026, those are the only two versions ever removed from axios, and the timestamps line up with the post-mortem's timeline. The same query against plain-crypto-js shows the staging pattern: 4.2.0 at 2026-03-30T05:57:32Z, 4.2.1 at 23:59:12Z, and npm's security placeholder at 2026-03-31T03:25:11Z.

Eleven weeks later the pattern repeated. On June 17, 2026, a single human account published malicious versions of 141 @mastra/* packages in about 80 minutes. As with axios, the package code was unchanged, and a single injected dependency carried the payload: easy-day-js, a dayjs typosquat uploaded clean the day before. We covered it as the Mastra compromise, and Microsoft attributed it with high confidence to Sapphire Sleet, the name it uses for the axios attacker. The second-stage implants in the two incidents also share a rare spoofed User-Agent, Internet Explorer 8 on Windows XP, which matters for detection below.

How does an APT get past 2FA and trusted publishing?

#

An APT gets past 2FA and trusted publishing by operating from the maintainer's own authenticated machine, where every identity check has already passed. Each control in the publish chain answers a question about identity or process. Once the attacker is the maintainer, the answers are all yes.

1. Maintainer workstationAttacker's RAT runs here with the maintainer's logged-in browser.
bypassed2FA and hardware keys: the RAT reuses sessions that already passed 2FA.
2. npm publishA personal-account publish, not the GitHub Actions trusted publisher used for the previous four 1.x releases.
bypassedTrusted publishing: the non-OIDC path to publish was still open.
3. Registry accepts the tarballaxios@1.14.1, no provenance attestation, one new dependency.
not enforcedProvenance: npm clients install packages without it by default.
heldBehavior analysis of the new dependency: flagged 16 minutes before axios used it.
4. Semver resolution^1.14.0 now resolves to 1.14.1 on any fresh install, npx run, or CI cache miss.
partialLockfile with npm ci: protects existing graphs, not new resolutions.
would hold24-hour release-age cooldown: the version was gone after 2 h 53 min.
5. Install on a developer laptop or CI runnerpostinstall runs node setup.js, which fetches a platform RAT.
would holdDependency lifecycle scripts off (npm v12 default since July 2026; pnpm 10 and later).
Each hop trusts the hop above it. An APT that owns hop 1 inherits every identity-based check below it.

The figure, hop by hop, with the cost of each control:

ControlWhat it checksAgainst axiosCost and gap
2FA and hardware keysA human proved identity at loginBypassed: the RAT reused already-authenticated sessionsEssential against phished passwords; no help once malware runs on the maintainer's machine
Trusted publishing (OIDC)The publish came from a named CI workflowBypassed: a personal-account publish was still acceptedDisallowing tokens closes the stolen-token path, not the stolen-laptop path
Provenance attestationWhich repository and workflow built the tarballAbsent from the malicious versions, and not required by npm clientsOnly useful if consumers reject unattested versions of packages that normally carry it
Lockfile with npm ciThe resolved dependency graph has not changedHeld for projects with committed lockfilesDoes not cover npx, global installs, new dependencies, or a regenerated lockfile
24-hour release-age cooldownThe version is old enough for others to have examined itWould have held: the window was 2 h 53 minDelays urgent security fixes; usually overridable per project
Dependency install scripts offLifecycle scripts from dependencies do not runWould have held against this dropperNative modules need allowlisting; loaders triggered at runtime are untouched
Behavior analysis of new versionsWhat the code actually doesHeld: the dependency was flagged before axios used itOnly protects installs that happen after the verdict lands

The verdict: identity controls raise the cost of stealing a token, and an APT is the one adversary willing to pay it. Controls that examine the artifact, or that delay it until someone else has examined it, are the ones that held. npm's own trusted publishing documentation "strongly recommend[s] restricting traditional token-based publishing access" once trusted publishing is on. That advice is right, and it would not have been enough. Express TC member Wes Todd warned that OIDC-based publishing "does not protect against a fully compromised machine."

Publisher metadata is a weaker signal than it looks. Here is who published the axios releases just before the attack:

Bash
$ curl -s https://registry.npmjs.org/axios | jq -r '.time as $t | .versions | to_entries[]
    | select(.key | test("^1\\.13\\.[4-6]$|^1\\.14\\.0$|^0\\.30\\.[23]$"))
    | "\($t[.key][0:10])  \(.key)\t\(.value._npmUser.name)\tprovenance=\(.value.dist.attestations != null)"' | sort
2025-09-27  0.30.2	jasonsaayman	provenance=false
2026-01-27  1.13.4	GitHub Actions	provenance=true
2026-02-08  1.13.5	GitHub Actions	provenance=true
2026-02-18  0.30.3	jasonsaayman	provenance=false
2026-02-27  1.13.6	GitHub Actions	provenance=true
2026-03-27  1.14.0	GitHub Actions	provenance=true

On the 1.x line, a personal-account publish without provenance broke a four-release pattern of GitHub Actions publishes. On the 0.x line, the same thing was normal: 0.30.3 came from the same personal account six weeks earlier, also with no provenance. A rule that alerts on publisher drift catches 1.14.1 and misses 0.30.4. A rule that reads the code catches both, because both pulled in the same dropper.

Three shapes of APT activity in package registries

#

APT operations against open source fall into three shapes, and each puts its persistence in a different place.

The long con is the rarest and the hardest to defend. The xz Utils backdoor took 28 months from the persona's first innocuous patch on October 29, 2021, to the first backdoored release, per Russ Cox's timeline, and it was caught by Andres Freund, a PostgreSQL developer, weeks after release. The persona has never been publicly attributed to a government, but the investment profile is an APT's.

The package factory is the most common shape by volume. In October 2025 we reported 338 malicious npm packages with over 50,000 cumulative downloads in a single Contagious Interview wave, published through more than 180 fake personas. Many were typosquatting everyday names like epxresso for express and dotevn for dotenv. As of April 2026, our campaign page tracked more than 1,700 packages tied to the operation, and the latest cluster had spread to five ecosystems: npm, PyPI, Go modules, crates.io, and Packagist. Related clusters keep appearing. PolinRider plants loaders in compromised legitimate repositories, and Famous Chollima hid one in a Packagist dev branch likely staged for a fake job interview.

The factory shape exposes a limit of install-time defenses. In that five-ecosystem cluster, most packages did not use install hooks at all. The loader sat behind a function that looked right for the package: Logger::trace in a Rust logging crate, find_by_key() in a license helper. Turning off lifecycle scripts stops axios-style droppers and does nothing against a logger that fetches a ZIP from Google Drive the first time you call it.

Side by side:

ShapeExampleEntryTime to payloadWhere persistence livesWhat catches it
Long conxz Utils backdoorYears of real contributions until the persona holds release rights28 monthsThe persona's reputationAnomalies in build output and runtime behavior; human review rarely
Maintainer takeoveraxios; MastraSocial engineering, then a RAT on the maintainer's machineWeeks of lures, hours of exposureThe actor's reusable playbookAnalysis of each new version before install; release-age cooldowns
Package factoryContagious Interview; PolinRiderTyposquats and fake projects delivered through job-interview luresInstall time, or the first call to a normal-looking functionHundreds of personas and fast re-uploads after takedownsBehavior analysis at publish; clustering by reused artifacts

The verdict: no single control covers all three. Behavior analysis of each new version is the only one that applies to every row, and the long con remains the shape most likely to get through.

How do you detect an advanced persistent threat?

#

You detect an advanced persistent threat in your dependencies by its behavior and its repetition, not its identity. By the time anyone names the actor, the package has usually been removed. Three layers work, in order of how early they fire.

Before install, read what the package does. The highest-signal behaviors in the incidents above are concrete: a patch release that adds a dependency less than a day old, an install hook that spawns a shell or downloads a binary, string tables decoded at runtime and passed to eval, NODE_TLS_REJECT_UNAUTHORIZED=0, and network calls from code that has no business making them, such as a logger or a license parser. Socket's malware detection flagged plain-crypto-js@4.2.1 at 00:05:41 UTC, six and a half minutes after it was published and 16 minutes before axios started depending on it. Socket Firewall applies verdicts like that one at install time, blocking known-malicious versions, including transitive dependencies, before the package manager downloads them. It cannot block a version nobody has flagged yet, which is why analysis speed after publish matters more than anything else in this list.

On hosts and networks, hunt for the implant's habits. Both the axios RAT and the Mastra second stage beaconed with the same spoofed Internet Explorer 8 User-Agent. Google's analysis named that "uncommon User-Agent string" as one of the links between the axios malware and earlier tooling from the same actor. In the axios RAT, the command and control (C2) beacon fired every 60 seconds. A crude hunt over proxy logs:

Bash
# Spoofed User-Agent shared by the axios RAT and the Mastra second stage
$ grep -iF 'msie 8.0; windows nt 5.1; trident/4.0' /var/log/squid/access.log

Expect noise from old scanners and embedded devices that still send that string. A hit from a developer workstation or a CI runner is worth an incident ticket.

Across waves, cluster the operator. APTs reuse what is expensive to rebuild: loader templates, archive names, extraction paths, XOR keys, wallet addresses, and personas. The five-ecosystem cluster reused one archive name, ecw_update.zip, and one extraction directory across Python, Go, Rust, and PHP. Mapping each finding to MITRE ATT&CK techniques such as T1195.002 (Compromise Software Supply Chain) makes those clusters comparable across vendors.

One warning about checking after the fact. A clean lockfile today does not prove you were safe on March 31. Code fetched by npx, a global install, or a fresh CI cache never touches your project lockfile, and the malicious versions no longer exist to reinstall. We wrote about this in the hidden blast radius of the axios compromise. The absence of evidence after the fact is not strong evidence of absence.

Why one APT has several names

#

The axios attacker has at least two public names. Google Threat Intelligence Group tracks it as UNC1069. Microsoft tracks it as Sapphire Sleet, using "Sleet" as the family name for North Korean actors. The two attributions were published about a day apart. Google's rested on malware lineage, a backdoor it calls WAVESHAPER.V2, and on C2 traffic from a VPN node the actor had used before.

NameUsed byWhat it refers to
UNC1069Google Threat Intelligence GroupThe axios attacker; financially motivated, North Korea-nexus, active since at least 2018
Sapphire SleetMicrosoftThe axios attacker, and the actor behind the Mastra compromise
Lazarus GroupMITRE ATT&CK (G0032)North Korean state-sponsored group, often used as an umbrella; associated names include Labyrinth Chollima, HIDDEN COBRA, ZINC, and Diamond Sleet
Contagious InterviewSocket and other researchersThe fake-recruiter package campaign, tracked by its playbook across many personas
Famous ChollimaIndustry reportingNorth Korean cluster tied to fake-job lures against developers

Attribution rarely changes triage. A malicious package is removed, blocked, and investigated the same way whoever wrote it. Attribution changes forecasts. Once Microsoft linked the Mastra compromise to the axios attacker in June, defenders knew the staged-dependency pattern was a playbook rather than a one-off, and that the User-Agent from March was worth hunting for again.

The open problem is the maintainer's laptop

#

Registries are closing the stolen-token path. npm v12 turned dependency install scripts off by default in July 2026. npm also announced a phase-out for 2FA-bypass tokens: from August 2026 they lose sensitive account and package actions, including editing trusted publishing settings, and direct publishing from them is expected to end around January 2027. pnpm 11 holds back versions younger than 24 hours by default. Two of those defaults, install scripts off and a 24-hour release age, would have stopped the axios dropper on a default install.

None of them addresses an attacker who is sitting in the maintainer's browser. We think registries should treat a change in publishing path as an event in itself: a package whose recent releases came from a trusted publisher should not accept a personal-account publish without a delay and a signal that consumers can see. That would not stop an APT. It would make the APT's moment of control visible before it reaches a CI runner. Until then, the controls that hold are the ones that never trusted the publisher in the first place. An APT can become the maintainer. It cannot make a dropper stop behaving like a dropper.