Atomic Arch: When “Abandoned but Trusted” Becomes the Attack Surface
First, kill the wrong panic. This is not a compromise of the official Arch repositories. There is no evidence the official repositories or the ISO were affected — core and extra are clean and the pacman signing chain held. A plain pacman -Syu against official mirrors pulled nothing malicious during this incident. If your only Arch exposure is official packages, you are not in scope — stop reading the IR runbook and go do something useful.
The compromise lives in the AUR, the Arch User Repository: community-contributed build recipes (PKGBUILDs) that helpers like yay and paru fetch and execute on your machine. That distinction is the whole story. The AUR has never pretended to be a trusted, signed channel; it’s explicitly a user-trust model where you’re supposed to read the PKGBUILD before you build. Most people don’t. The attackers knew that.
And the second correction, because someone in your channel is already asking: there is no CVE here — none assigned in the NVD as of this writing, and no fixed version to bump to. Sonatype, which discovered the campaign and named it “Atomic Arch,” tracks it under its own advisory ID (Sonatype-2026-003775) and scored it CVSS 8.7 — but that’s a vendor catalog entry, not an NVD/CVE record, and there is nothing to “upgrade” to. This is malware riding a governance gap, not a product vulnerability. “What’s the patch?” is the wrong question, and if your remediation plan has a version-bump field in it, you’ve already misunderstood the incident. Remediation here means find exposure, purge, rebuild, rotate everything — and if root was reached, reinstall from known-good media. More on that below.
The orphan-adoption gap is the real vulnerability
Here’s the part worth sitting with. The attackers didn’t pop a server, didn’t phish a maintainer (at least not as the primary vector), didn’t find a pacman parsing bug. They used the AUR’s legitimate orphan-adoption process. When an AUR maintainer walks away, their packages become orphaned — still installed on plenty of machines, still pulled by helpers, but unowned. The AUR lets anyone request adoption of an orphan. That’s by design; it’s how the community keeps useful packages alive.
So the attackers requested ownership of abandoned-but-still-used packages, got it, and rewrote the PKGBUILDs. No exploit. The trust was already banked; they just acquired it. Sonatype’s writeup put it well — the actors weren’t building trust from scratch, they were acquiring projects that had already earned it.
That generalizes, and that’s why this matters even if you’ve never touched Arch. “Abandoned but trusted” is a supply-chain surface everywhere. Dormant npm packages with a million weekly downloads and a maintainer who lost interest in 2021. PyPI projects one social-engineering email away from a handoff. Browser extensions sold to “analytics” shells. The Arch specifics are local color; the pattern is ecosystem-agnostic. Anywhere accumulated trust outlives active stewardship, someone can inherit it.
The execution chain, kept at altitude
The mechanism, without giving anyone a build sheet:
A weaponized PKGBUILD (and/or its companion .install file) was modified to fetch and npm install a malicious Node package — primarily atomic-lockfile, with later variants pulling Bun-based packages like js-digest, lockfile-js, and nextfile-js. The atomic-lockfile@1.4.2 package carried a preinstall lifecycle hook that ran src/hooks/deps, executing a roughly 3 MB stripped Rust ELF. npm runs preinstall automatically unless you’ve set --ignore-scripts, so the hook fired whenever that malicious package-manager command ran — and where it ran is the whole question. In many documented first-wave cases the npm install lived in the package’s .install scriptlet (a post_install() hook), which executes during pacman‘s install/upgrade step — not during the makepkg build phase. In other cases it sat in a PKGBUILD function (prepare()/build()/package()), which runs at build time. Either way it fired before you saw anything wrong — but that distinction decides your blast radius, as we’ll see.
The one IOC worth pinning to your wall, the payload binary:
SHA-256: 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B
Note the JavaScript-runtime dependency showing up in a package that has no business needing one. A PKGBUILD for some C utility suddenly reaching for npm or Bun is exactly the kind of smell that should stop a build (CM-3 — config change control on the recipe before you trust it).
On scope, the count was a moving target and you should present it that way — and attribute each number, because they got conflated fast. Sonatype initially described a small set (on the order of a few dozen) that expanded rapidly. Community and media trackers then moved through 400+ (StepSecurity’s headline figure, not Sonatype’s), past 900 mid-incident, to 1,500+ (PrivacyGuides, SecurityWeek), with community tooling later cataloguing 1,600+. So: a few dozen climbing past 1,600 across waves. Not a flat “thousands,” and the spread between numbers is itself the signal — this was being counted live while the attacker kept moving. Affected blast radius includes Arch and AUR-consuming derivatives (EndeavourOS; Manjaro if you’ve enabled the AUR), Arch-based self-hosted CI runners, and — the one people forget — developers running Arch under WSL2.
The infostealer, and why “rotate everything” is literal
The Rust payload is a credential stealer with uncomfortable reach. It goes after developer infrastructure first: SSH private keys and known_hosts, GitHub tokens, npm credentials, Docker and Podman auth files, HashiCorp Vault tokens. Then browser data across every Chromium-derived browser you can name — Chrome, Edge, Brave, Vivaldi, Opera — cookies, local storage, LevelDB, with the ability to decrypt encrypted cookies. Then comms: Slack, Teams, Discord (all variants), Telegram, plus VPN config files. Then the quiet stuff — shell histories from bash, zsh, and fish, and .env files full of API keys.
The detail that should change your urgency calculus: the malware validated stolen tokens by calling Slack, Teams, Discord, GitHub, and OpenAI APIs directly. It confirmed which credentials were live. So if a host was exposed, you don’t treat the tokens as “maybe compromised.” You treat them as known-stolen and verified-working by the adversary. That’s an IA-5 problem with the safety off — rotate SSH keys, GitHub/npm/cloud/Vault tokens, browser sessions, chat tokens, anything the build environment could read. All of it.
The eBPF rootkit changes the answer from “clean it” to “rebuild it”
This is where Atomic Arch stops being a run-of-the-mill stealer.
The payload only deploys its kernel component when it runs as root, or with enough capability (effective CAP_BPF/CAP_SYS_ADMIN) to load a BPF program — it is not a privilege-escalation tool, it activates only when it already has the rights. So your exposure depends entirely on where the malicious command lived and how it ran. If it sat in a PKGBUILD function, it ran during makepkg under your build user (fakeroot context) — user-context credential theft. If it sat in a .install scriptlet, it ran during pacman‘s install/upgrade step, which the helper drives through sudo as root — so a routine yay -S that builds and installs may have handed root to the recipe. Any path where the payload ran as root, or with effective BPF/admin capabilities, should be treated as possible eBPF-rootkit exposure. That distinction drives your remediation tiers: a host where the malicious path never executed is fine; user-context theft can be cleaned and rotated; root-context execution cannot be trusted again. And “built but never installed” is not automatically safe — it’s theft-free only if the malicious logic lived solely in the .install; if it was in a PKGBUILD function, building alone already meant user-context credential theft.
What the rootkit does: loads an eBPF program that hides its own processes, process names, and socket inodes from userspace. It pins state in BPF maps at /sys/fs/bpf/hidden_pids, /sys/fs/bpf/hidden_names, and /sys/fs/bpf/hidden_inodes. It conceals PIDs from /proc, hides sockets from netstat, ss, and /proc/net/tcp, and includes anti-ptrace logic that kills anything trying to attach a debugger to a hidden process.
Sit with the SI-7 implication. Once that program is loaded, your userspace telemetry is lying to you. ps is lying. ss is lying. An EDR agent that enumerates processes through the same kernel interfaces the rootkit hooks can be blinded right along with everything else. So “our EDR saw nothing” is not reassurance — it’s consistent with exactly the thing you’re afraid of. That’s the SI-4 caveat you need to say out loud in the bridge call, because someone will offer the clean EDR dashboard as evidence of safety, and it isn’t.
The honest detection angle is artifacts the rootkit can’t easily hide from, plus out-of-band inspection. Check for the pinned maps:
ls -la /sys/fs/bpf/hidden_*
Check for unexpected systemd units (the malware drops one — /etc/systemd/system/ for root installs, ~/.config/systemd/user/ for per-user, both with Restart=always and RestartSec=30, so it respawns 30 seconds after you kill it). But understand the limits: a sufficiently careful rootkit can hide its own map artifacts too, and the second wave added obfuscation — shell string-splitting, hex escapes — specifically to harden evasion. So the real answer for a plausibly-root-compromised host is boot-time or offline inspection from known-good media, not live poking at a system whose kernel may be hostile.
The C2, for your blocklists and your threat-hunt: a Tor hidden service, olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion, XOR-decoded at runtime rather than stored in plaintext. Beacon to POST /api/agent; file exfil staged through temp.sh via POST /upload. It runs a SOCKS5 loopback proxy whose local listener — naturally — becomes invisible once the eBPF layer is active. So scanning for the local listener works right up until the rootkit loads, and then it doesn’t.
Remediation as tiers, not a patch
Map it to controls and work it in order.
Exposure triage (CM-8 / RA-5). Did this host install or update any AUR package during the window? If you can answer no with confidence, official-repo systems are out of scope and you’re done. Check your helper logs and pacman history. This is also the moment you discover whether you actually have a component inventory for developer workstations, or whether you’ve been pretending.
Inspect before trusting the next update (CM-3 / SR). Read both the PKGBUILD and the .install before building — a clean-looking PKGBUILD can still reference a malicious .install that executes at install/upgrade time after the package is built. Inspect source=(), makedepends=(), the prepare()/build()/check()/package() functions, and the post_install()/post_upgrade() scriptlets. Unexpected network fetches, unexpected npm install/Bun invocations, a JavaScript runtime appearing in a package that never needed one — all stop-the-build signals. The community moved fast here; the lenucksi/aur-malware-check repo consolidated detection tooling from scattered Gists. Useful, but it’s community tooling, unofficial — read it before you run it, same discipline that would’ve caught the PKGBUILD.
Pre-empt the trigger (CM-7). Disable lifecycle scripts across every package manager that can show up in a recipe — npm, pnpm, yarn, and Bun. The later wave moved to Bun and to renamed packages, so npm-only mitigation is partial. Set ignore-scripts and verify it in the context that will actually run the command — including root’s environment/config if the command lives in a .install scriptlet, because the user’s npm config doesn’t apply to a command pacman runs as root. This is a real layer worth defaulting on, but not a cure: a malicious PKGBUILD or .install can execute arbitrary code without any npm lifecycle hook, and a module that gets require()d at runtime still executes. Least functionality buys you a layer, not immunity.
Assume theft and rotate (IA-5). Covered above. The malware validated the tokens; treat them as gone.
Decide clean-vs-rebuild honestly (SI-7 / SI-3). Unprivileged-only exposure: purge the package, rebuild from a clean recipe, rotate, monitor. Any plausible root exposure: reinstall from known-good media. I know nobody wants to hear “nuke the workstation,” and I know there’s always pressure to declare it cleaned and move on. Resist it. Once a kernel-level rootkit may have run, the live system can’t certify its own cleanliness — the thing you’d use to check is the thing that’s been subverted.
Don’t moralize about Arch over this. The AUR’s trust model is documented and deliberate; the social contract is that you review what you build. The failure wasn’t that the model is reckless — it’s that “review every PKGBUILD” is a control that depends entirely on human diligence at the exact moment people are least diligent, running an update before coffee. Every ecosystem with inheritable trust and automatic build-time execution has a version of this hole. Arch just had its name on it in June.
Sources
- Atomic Arch npm campaign adds malicious dependency (Sonatype)
- 400+ AUR packages hijacked: what the “Atomic Arch” campaign means for supply-chain security (StepSecurity)
- Around 1,500 AUR packages compromised with “rootkit-like” malware (PrivacyGuides)
- aur-malware-check: detection tools for the June 2026 atomic-lockfile AUR supply-chain attack (GitHub / lenucksi)
- Over 400 Arch Linux AUR packages hijacked to deploy infostealer and eBPF rootkit (The Hacker News)
- Over 400 Arch Linux packages compromised to push rootkit, infostealer (BleepingComputer)
- Atomic Arch supply chain attack hits 1,500 AUR packages (SecurityWeek)
- Atomic Arch hijacks Linux AUR packages to deliver malware (Hackread)