# 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

Source: https://socket.dev/glossary/advanced-persistent-threat-apt

Last updated: 2026-10-30

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](https://socket.dev/blog/axios-npm-package-compromised). 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](https://socket.dev/glossary/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](https://csrc.nist.gov/glossary/term/advanced_persistent_threat), 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](https://www.uscc.gov/sites/default/files/3.26.12bejtlich.pdf) 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 cybercriminal | Advanced persistent threat | Cyberwarfare operation |
| --- | --- | --- | --- |
| Who picks the target | Whoever is exposed or installs the bait | Chosen in advance, then researched | Chosen for strategic effect |
| Primary goal | Money, quickly | Long-term access, intelligence, or, for North Korea, revenue | Disruption or destruction |
| After a takedown | Moves on to a softer target | Returns with new personas and infrastructure | Waits for the next opening or escalates |
| Typical open source form | Credential-stealing typosquats published in bulk | Maintainer takeover through fake job or partnership lures | Rare in package registries to date |

One organization can do all three. We have tracked [Lazarus Group](https://socket.dev/blog/north-korean-apt-lazarus-targets-developers-with-malicious-npm-package) publishing malicious npm packages aimed at developers. [MITRE ATT&CK](https://socket.dev/glossary/adversarial-tactics-techniques-and-common-knowledge-att-and-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](https://attack.mitre.org/groups/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](https://cloud.google.com/blog/topics/threat-intelligence/north-korea-threat-actor-targets-axios-npm-package/). Our research on [the Contagious Interview campaign](https://socket.dev/supply-chain-attacks/north-korea-s-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](https://socket.dev/glossary/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](https://socket.dev/blog/axios-npm-package-compromised).

The attack on axios did not exploit npm. It exploited a person. The lead maintainer, Jason Saayman, [wrote in the post-mortem](https://github.com/axios/axios/issues/10636) 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)](https://socket.dev/glossary/remote-access-trojans-rats) 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](https://socket.dev/blog/attackers-hunting-high-impact-nodejs-maintainers): 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](https://socket.dev/blog/attackers-hunting-high-impact-nodejs-maintainers), 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.

> Sources: Socket research, the axios post-mortem, npm registry timestamps, Google Threat Intelligence Group, Microsoft Threat Intelligence.
>
> Timeline of the axios compromise, March 5 to June 17, 2026

The timeline above in text form, all times UTC:

| Time (UTC) | Event | Source |
| --- | --- | --- |
| Mar 5, 2026 | A fake Openfort representative contacts Node.js core collaborator Jean Burellier on LinkedIn, then invites him to private Slack workspaces | Socket reporting |
| Mar 10–11 | Slack invites from an account impersonating Openfort's co-founder reach other npm maintainers | Socket reporting |
| About Mar 17 | The axios lead maintainer's PC is compromised through social engineering and RAT malware, about two weeks before the publish | axios post-mortem |
| Mar 30, 05:57:32 | plain-crypto-js@4.2.0 published: a clean copy of crypto-js with no payload | npm registry |
| Mar 30, 23:59:12 | plain-crypto-js@4.2.1 published with a postinstall dropper | npm registry |
| Mar 31, 00:05:41 | Socket's automated detection flags plain-crypto-js@4.2.1 as malware | Socket reporting |
| Mar 31, 00:21:58 | axios@1.14.1 published with plain-crypto-js added as a dependency | npm registry |
| Mar 31, 01:00:57 | axios@0.30.4 published with the same dependency | npm registry |
| Mar 31, 01:38 | An axios collaborator opens a pull request to deprecate the versions and contacts npm | axios post-mortem |
| Mar 31, 03:15 | npm removes both malicious axios versions; exposure window 2 h 53 min | axios post-mortem |
| Mar 31 | Google Threat Intelligence Group attributes the compromise to UNC1069 | Google |
| Apr 1 | Microsoft attributes the compromise to Sapphire Sleet | Microsoft |
| Jun 17 | 141 @mastra/\* packages published with an injected easy-day-js dependency; Microsoft later attributes it to Sapphire Sleet | Socket 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](https://socket.dev/glossary/postinstall-scripts), 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`](https://socket.dev/blog/axios-npm-package-compromised) 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`](https://socket.dev/blog/mastra-npm-packages-compromised), a dayjs typosquat uploaded clean the day before. We covered it as [the Mastra compromise](https://socket.dev/supply-chain-attacks/mastra-ai-framework-compromise), and [Microsoft attributed it with high confidence](https://www.microsoft.com/en-us/security/blog/2026/06/17/postinstall-payload-inside-mastra-npm-supply-chain-compromise) 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.

> Each hop trusts the hop above it. An APT that owns hop 1 inherits every identity-based check below it.
>
> The npm publish trust chain and which controls held during the axios compromise

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

| Control | What it checks | Against axios | Cost and gap |
| --- | --- | --- | --- |
| 2FA and hardware keys | A human proved identity at login | Bypassed: the RAT reused already-authenticated sessions | Essential against phished passwords; no help once malware runs on the maintainer's machine |
| Trusted publishing (OIDC) | The publish came from a named CI workflow | Bypassed: a personal-account publish was still accepted | Disallowing tokens closes the stolen-token path, not the stolen-laptop path |
| Provenance attestation | Which repository and workflow built the tarball | Absent from the malicious versions, and not required by npm clients | Only useful if consumers reject unattested versions of packages that normally carry it |
| Lockfile with npm ci | The resolved dependency graph has not changed | Held for projects with committed lockfiles | Does not cover npx, global installs, new dependencies, or a regenerated lockfile |
| 24-hour release-age cooldown | The version is old enough for others to have examined it | Would have held: the window was 2 h 53 min | Delays urgent security fixes; usually overridable per project |
| Dependency install scripts off | Lifecycle scripts from dependencies do not run | Would have held against this dropper | Native modules need allowlisting; loaders triggered at runtime are untouched |
| Behavior analysis of new versions | What the code actually does | Held: the dependency was flagged before axios used it | Only 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](https://docs.npmjs.com/trusted-publishers) "strongly recommend\[s\] restricting traditional token-based publishing access" once [trusted publishing](https://socket.dev/glossary/npm-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](https://socket.dev/blog/how-to-use-socket-to-find-out-if-you-were-affected-by-the-backdoored-xz-package) took 28 months from the persona's first innocuous patch on October 29, 2021, to the first backdoored release, [per Russ Cox's timeline](https://research.swtch.com/xz-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](https://socket.dev/blog/north-korea-contagious-interview-campaign-338-malicious-npm-packages) in a single Contagious Interview wave, published through more than 180 fake personas. Many were [typosquatting](https://socket.dev/glossary/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](https://socket.dev/blog/contagious-interview-campaign-spreads-across-5-ecosystems): npm, PyPI, Go modules, crates.io, and Packagist. Related clusters keep appearing. [PolinRider](https://socket.dev/supply-chain-attacks/polinrider) plants loaders in compromised legitimate repositories, and [Famous Chollima](https://socket.dev/blog/famous-chollima-targets-php-developers-through-compromised-packagist-package) 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:

| Shape | Example | Entry | Time to payload | Where persistence lives | What catches it |
| --- | --- | --- | --- | --- | --- |
| Long con | xz Utils backdoor | Years of real contributions until the persona holds release rights | 28 months | The persona's reputation | Anomalies in build output and runtime behavior; human review rarely |
| Maintainer takeover | axios; Mastra | Social engineering, then a RAT on the maintainer's machine | Weeks of lures, hours of exposure | The actor's reusable playbook | Analysis of each new version before install; release-age cooldowns |
| Package factory | Contagious Interview; PolinRider | Typosquats and fake projects delivered through job-interview lures | Install time, or the first call to a normal-looking function | Hundreds of personas and fast re-uploads after takedowns | Behavior 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`](https://socket.dev/blog/axios-npm-package-compromised) 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](https://socket.dev/blog/hidden-blast-radius-of-the-axios-compromise) 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)](https://socket.dev/glossary/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](https://socket.dev/blog/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](https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/) tracks it as Sapphire Sleet, using ["Sleet" as the family name](https://learn.microsoft.com/unified-secops-platform/microsoft-threat-actor-naming) 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.

| Name | Used by | What it refers to |
| --- | --- | --- |
| UNC1069 | Google Threat Intelligence Group | The axios attacker; financially motivated, North Korea-nexus, active since at least 2018 |
| Sapphire Sleet | Microsoft | The axios attacker, and the actor behind the Mastra compromise |
| Lazarus Group | MITRE 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 Interview | Socket and other researchers | The fake-recruiter package campaign, tracked by its playbook across many personas |
| Famous Chollima | Industry reporting | North 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](https://socket.dev/blog/npm-12) 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](https://socket.dev/blog/pnpm-11-adds-new-supply-chain-protection-defaults) 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.
