Atomic Arch or Atomic OUCH?!
1,500+ AUR packages got hijacked to ship a credential stealer with an eBPF rootkit. What a shit Friday. I hope you enjoyed my title wordplay, you devious devil.
On 11 June 2026, someone worked out that the cheapest way into a few hundred thousand developer machines was to wait for people to stop maintaining their software, then quietly take it over.
That's the whole attack. Yup.
Bring back Print Nightmare. At least that shit was cool.
What's left is a trust model with a hole in it and someone patient enough to sit by that hole. I'm sure there's a 'your mum' joke in there somewhere.
If you run Arch, an Arch derivative, or anything that pulls from the AUR (CachyOS, EndeavourOS, Manjaro's AUR opt-in, take your pick), this one is worth ten minutes of your evening.
There is a script in this post somewhere. You can't download it, because there is a far better community repo geared up to fight this thing - it'll be linked at the bottom.
What happened
The AUR (Arch User Repository) is a repo for community build scripts, not vetted software. You knew that. The deal has always been to read the PKGBUILD (RTFM? RTFPB?), trust the maintainer, accept that makepkg runs arbitrary code on your box as part of normal operation. Sounds sketchy. Usually isn't. People are adults... sometimes. It also makes the AUR repo a phenomenal target should you get access.
The attacker found orphaned packages (according to sources - I'm just the guy writing yet another fucking article because I can).
Legitimate projects that had been abandoned by their original maintainers, kept their name, kept their install base, kept their reputation, and were sitting there waiting for adoption through AUR's standard ownership-transfer process. The attacker adopted them. From your side, paru shows you a familiar package from a familiar source. Nothing looks new because nothing is new, except the person now holding the keys. You didn't notice that you're compromised because you didn't check the PKGBUILD. That's okay. You live, you learn.
Sonatype, who named the campaign Atomic Arch (I still prefer Atomic Ouch) put it better than I ever could.
Traditional supply chain attacks try to convince you to install something new. This one inherited trust that already existed.
Their tracking ID is Sonatype-2026-003775, CVSS 8.7. Eight point seven. Fucking hell.
Two waves have been confirmed so far:
- Wave one:
atomic-lockfile(npm), pushed via accounts impersonating a legitimate maintainer. - Wave two:
js-digestandlockfile-js, delivered through bun-based install paths rather than npm alone, run by a separate set of attacker accounts.
Both waves deliver the same class of payload. Sonatype's initial writeup said "at least 20 orphaned packages," the broader OSINT tracking (IFIN, Whanos, the community gists) put it north of 400 by the evening, and Sonatype's 12 June update then revised the estimate to roughly 1,500 packages across the combined waves.
Treat that as preliminary and probably still climbing rather than final. Affected names that have surfaced include guiscrcpy, netmon-git, inadyn-mt, nodejs-elm, and keepassx2, among many more. The live list is being maintained by the community (links at the end) because it is still moving. You should probably go visit their site/repos, as this is just my take.
Clever girl...
The hijacked PKGBUILD does not contain malware. You can read the whole thing top to bottom and find nothing wrong, which is exactly the idea. So... the advice that I gave you earlier to read the PKGBUILD was a bit shit, really.
Do it anyway.
What the attacker added was a single line in the .install or .hook file that runs during install:
npm install atomic-lockfile
(Some variants ran npm install atomic-lockfile minimist chalk, padding the malicious package with two legitimate ones to look like an ordinary dependency pull.)
So the PKGBUILD fetches a clean-looking npm package from the public registry. Signature-based scanners looking at the AUR package find nothing, because the bad thing isn't in the AUR package. It's one hop downstream, on npm.
Then atomic-lockfile@1.4.2 does its thing. It is mostly a real TypeScript package, with one extra wired into package.json:
"preinstall": "./src/hooks/deps"
./src/hooks/deps is not a script. It is a 3 MB stripped Rust ELF binary sitting in the package source tree. The preinstall lifecycle hook executes it automatically the moment npm touches the package, before you've even finished installing whatever you actually wanted. No further interaction. Build the AUR package, install the AUR package, run it in CI, doesn't matter. The binary runs.
So the chain is:
You: paru -S some-orphaned-package
-> PKGBUILD runs (looks clean)
-> npm install atomic-lockfile (clean-looking package)
-> npm preinstall hook fires
-> ./src/hooks/deps executes (3 MB Rust stealer)
Three layers of indirection, each one looking unremarkable to the layer above it. That's how it stayed quiet long enough to reach a few hundred packages before anyone joined the dots.
What deps does to you
Whanos at ioctl.fail did a static reverse-engineering pass on the binary the same day. Their writeup is the canonical technical source and worth reading in full. The short version: deps is a Linux credential stealer with optional, root-only eBPF rootkit capabilities. It's built for developer workstations and build hosts. People like us, in other words: a fish history full of ssh commands, a browser full of live sessions, a Vault token sitting in a dotfile because rotating it was a tomorrow problem.
It steals, roughly, everything a developer touches:
- Browser and Electron app data: cookies, LevelDB local storage, encrypted cookie values, across what looks like every Chromium fork in existence plus their Flatpak and Snap variants.
- Session tokens for Slack, Microsoft Teams (skypeToken, bearer tokens, tenant metadata), Discord (and basically every third-party Discord client: Vesktop, Legcord, WebCord, Vencord, the lot).
- GitHub PATs, npm tokens, OpenAI/ChatGPT account metadata.
- SSH private keys and config, PuTTY keys,
known_hosts. - HashiCorp Vault tokens.
- Docker and Podman registry credentials and command history.
- VPN profiles and
.ovpnfiles. - Shell history for bash, zsh, and fish, specifically grepping for
ssh,rsync,scp,docker, and friends.
It validates the stolen creds live by hitting the real Slack, Teams, Discord, GitHub, and npm APIs with them. Those endpoints in the IOC list are not attacker infrastructure, they're the malware checking that what it just nicked actually works.
Exfil goes out in two channels. File content uploads to temp.sh via POST /upload. Command-and-control runs over a Tor onion service (POST /api/agent), reached through a local loopback SOCKS transport on 127.0.0.1, with the binary carrying its own bundled tor. The onion address is XOR-encoded in the binary, not plaintext, so a naive strings grep won't find it.
Persistence is a systemd service. As root it copies itself somewhere under /var/lib/ and writes a unit to /etc/systemd/system/. As a normal user it lands in your home and writes a user unit to ~/.config/systemd/user/. Both use the same template:
Restart=always
RestartSec=30
The rootkit only loads if you ran the thing with root or CAP_BPF/CAP_SYS_ADMIN. It checks geteuid() and parses CapEff: from /proc/self/status before bothering. If the gate passes, it loads an embedded eBPF object that hides PIDs, hides filenames from directory listings, hides socket inodes from /proc/net/tcp and netlink socket diagnostics, and kills ptrace attempts against its own hidden processes. The pinned maps live at /sys/fs/bpf/hidden_pids, hidden_names, and hidden_inodes.
There is also a half-finished staging path that pulls a second binary from the onion (/bin/linux, with /bin/sha256/linux returning the expected hash). Whanos thinks it's a cryptominer. There's a /usr/bin/monero-wallet-gui reference in the binary that supports that read. Not fully analysed yet.
And if the eBPF component loaded, your live-response tools are lying to you. ps, ss, ls, anything that reads /proc can be told to skip the malware's own artifacts. The running system can't be trusted to describe itself accurately, which is the whole reason every sane writeup of a root-level hit tells you to reinstall and means it.
Am I exposed?
Probably not. Do you use AUR? Maybe you should check anyway. I daily-drive CachyOS on paru, so I had a personal stake in that answer and didn't much enjoy sitting on "probably."
If you are an unfortunate recipient of this attack, you should probably consider yourself compromised and rotate all credentials and revoke all active sessions. Standard operating procedure with a credential stealer.
Also, nuke it from orbit. It's the only way to be sure.
You're at meaningful risk only if you installed or updated one of the affected AUR packages in roughly the last day or two, OR you've ever pulled atomic-lockfile, js-digest, or lockfile-js through any other route. Phoronix's read of the early telemetry put the wave-one npm install count in the low hundreds (around 317), but that was wave one only and before Sonatype's package estimate climbed toward 1,500. Treat the low-hundreds figure as a floor, not a ceiling, and "small" is no comfort if you're on the list.
The non-negotiables:
- Check whether you installed any affected package.
pacman -Qmlists your foreign (AUR) packages. Cross-reference against the live list. - Check your pacman history, not just current state.
/var/log/pacman.logremembers what you installed even after you removed it. Removal does not undo apreinstallhook that already ran. - Check for the persistence and rootkit artifacts directly, because a clean package list does not prove a clean box if the rootkit is hiding things.
I wrote a script that does all of that. It is read-only. It does not phone home. It checks the live infected-package list, your installed set, your pacman history, systemd units matching the dropper profile, ELF files matching the known payload hash, the pinned eBPF maps, running processes executing from suspicious or deleted paths, and the on-disk C2 strings. It splits "this string is in an ELF" (a real signal) from "this string is in a text file" (probably your notes, or this very article), so it won't scare you with its own IOC list.
I'll probably host it somewhere if I get requests for it. Otherwise, the official response repo would probably be a better shout. Community maintained and probably far better than this. I'm only keeping this here because.. well. I made a thing.
It's mine. It might not be a very good thing tomorrow after I wake up, but it is perfectly adequate. Cute, also.
# read-only, exit 0 = clean, 2 = indicators found, 1 = a check couldn't run
# run as root for full coverage (other homes, bpftool, /proc/*/exe)
curl -fsSL https://grdnr.net/aur-malware-scan.sh -o aur-malware-scan.sh
less aur-malware-scan.sh # read it first, obviously, given the news
sudo bash aur-malware-scan.sh

If it flags anything, we assume the host is compromised, and (more importantly) we do not type any credentials into it, isolate it from the network, and rotate everything from a different machine.
Then reinstall. After dousing it in gasoline and lighting it on fire.
If it still works after that.
What to rotate if you were hit
In rough priority order, because the malware specifically targets these:
- npm tokens and GitHub PATs (first, these spread the attack)
- SSH private keys and passphrases (regenerate, don't just rotate the passphrase)
- Slack, Teams/M365, Discord sessions (full sign-out-everywhere, then change password + re-MFA)
- HashiCorp Vault tokens
- Docker/Podman registry credentials
- VPN profiles
- Anything that ever appeared in your shell history in plaintext
The lesson, such as it is
There isn't a satisfying fix, which is the uncomfortable part. The AUR's trust model is also the AUR's value: it's fast, it's comprehensive, it carries the stuff the official repos won't. The same openness that makes it useful is the openness that let an orphaned package change hands and turn into a rootkit dropper.
What you can do is smaller and more boring than anyone wants:
- Read the PKGBUILD. Every time.
paruand friends show it to you for a reason. The malicious line was a literalnpm install atomic-lockfilesitting in plain sight in a.installfile. - Enable diff/review prompts in your AUR helper and actually look at the diff on updates, not just fresh installs. This attack landed on updates to packages people already trusted.
- Be suspicious of orphaned-then-suddenly-active packages. A dead project that just got adopted and pushed an update is exactly the shape of this attack.
- Use
--ignore-scriptsfor npm anywhere you're touching untrusted packages, and sandbox your builds where you can.
None of this is new advice. It's the same stuff that's been quietly ignored for years, because reading every PKGBUILD is boring and, right up until yesterday, skipping it cost nothing. This sort of shit has happened before, it has happened again, and it'll probably happen in the future.
Go read a PKGBUILD. You know you want to. You goofy goober, you.
Sources and live references
- Whanos, ioctl.fail - the canonical malware analysis of
deps: https://ioctl.fail/preliminary-analysis-of-aur-malware/ - Sonatype - "Atomic Arch" campaign naming, the orphaned-package trust-model writeup, and the 12 June update adding js-digest / lockfile-js and the ~1,500 estimate: https://www.sonatype.com/blog/atomic-arch-npm-campaign-adds-malicious-dependency
- BleepingComputer - incident coverage and the cross-source picture: https://www.bleepingcomputer.com/news/security/over-400-arch-linux-packages-compromised-to-push-rootkit-infostealer/
- Community detection-tools repo (consolidated gists, IOC list, affected packages): https://github.com/lenucksi/aur-malware-check
- Arch
aur-generalmailing list thread - official maintainer response and reporting: https://lists.archlinux.org/archives/list/aur-general@lists.archlinux.org/
Key indicators of compromise
Malicious packages : atomic-lockfile @ 1.4.2 (npm, wave 1)
js-digest, lockfile-js (bun, wave 2 - payload hashes not yet public)
Lifecycle hook : "preinstall": "./src/hooks/deps"
Payload path : src/hooks/deps
Payload SHA256 (w1) : 6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b
Payload MD5 (w1) : 42b59fdbe1b72895b2951412222ebf40
Payload size (w1) : 3,040,376 bytes (Linux ELF64, x86-64, PIE, Rust)
C2 (onion) : olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion
C2 callback : POST /api/agent (via 127.0.0.1 loopback SOCKS)
Exfil : temp.sh, POST /upload
Persistence : Restart=always / RestartSec=30 unit, ExecStart under /var/lib or ~/.config/systemd/user
eBPF rootkit maps : /sys/fs/bpf/{hidden_pids,hidden_names,hidden_inodes}
Staging : <onion>/bin/linux (suspected cryptominer), /usr/bin/monero-wallet-gui reference