Atomic Arch: AUR Package Takeover Delivers Infostealers and eBPF Rootkits
Attackers adopted orphaned Arch User Repository (AUR) packages using forged commit signatures to inject npm and bun dependency executions. The rogue packages 'atomic-lockfile' and 'js-digest' delivered a Rust credential stealer, systemd persistence, and an eBPF rootkit.
On this page 0% read
Executive Summary
Between June 9 and June 12, 2026, a widespread supply chain campaign named “Atomic Arch” targeted the Arch User Repository (AUR) community [Sources 1, 3, and 4]. By exploiting the AUR’s open adoption workflow for orphaned packages, attackers took ownership of more than 400 community-contributed packages [Sources 3 and 4].
The attackers utilized forged git commit signatures to impersonate trusted maintainers—most notably forging commits under the identity of legitimate developer arojas [Sources 3 and 6]. The tampered packages were modified to fetch and execute rogue npm/bun modules: first deploying atomic-lockfile via npm and later transitioning to js-digest via bun [Sources 1, 3, and 4]. Both modules carried a compiled Rust-based Linux ELF executable designed to exfiltrate developer secrets, browser cookies, SSH keys, Discord sessions, and Vault secrets [Sources 1 and 2].
Responders should note that if these packages were built or installed with root privileges (e.g. running an AUR helper like yay or paru under sudo), the payload deployed an eBPF-based rootkit [Sources 2 and 4]. This rootkit hooks system calls to hide process IDs, files, and socket connections, rendering standard process monitoring utilities like ps, top, and netstat blind [Source 2]. Consequently, compromised systems must be treated as untrusted, and full reinstallation from clean media is strongly recommended [Sources 1 and 4].
Key Facts
threat_type: "AUR package takeover supply chain attack"
ecosystem: "aur, arch, npm, bun"
campaign_name: "Atomic Arch"
disclosed: "2026-06-11"
execution_trigger:
- "PKGBUILD build-time dependency execution"
- ".install or .hook script execution during package install"
known_affected_packages:
- "alvr"
- "guiscrcpy"
- "netmon-git"
- "inadyn-mt"
- "nodejs-elm"
- "keepassx2"
affected_packages:
- "atomic-lockfile"
- "js-digest"
package_versions:
- "[email protected]"
- "[email protected]"
payload_files:
- "deps"
- "src/hooks/deps"
payload_hashes_sha256:
atomic_lockfile_payload: "6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b"
js_digest_payload: "7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316"
cryptominer_payload: "47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204"
network_iocs:
- "olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion"
- "temp[.]sh"
- "hxxps://temp[.]sh/upload"
runtime_iocs:
- "/sys/fs/bpf/hidden_pids"
- "/sys/fs/bpf/hidden_names"
- "/sys/fs/bpf/hidden_inodes"
- "/usr/bin/monero-wallet-gui"
- "~/.npm/_cacache/"
- "~/.bun/install/cache/"
credentials_at_risk:
- "GitHub personal access tokens and CLI credentials"
- "npm publishing tokens"
- "Discord authentication tokens"
- "Slack sessions"
- "SSH private keys"
- "HashiCorp Vault tokens"
- "Docker / Podman registry credentials"
- "Local cryptocurrency wallet files (Exodus, Monero)"
- "Browser credentials and session cookies"
Source Confidence & Evidence Mapping
- confirmed: The Arch User Repository maintainers confirmed the campaign, removed compromised versions, and flagged multiple malicious maintainer accounts:
krisztinavarga,custodiatovar, andveramagalhaes[Sources 1, 3, and 4]. - confirmed: Developer David Runge and community audits proved that commits referencing legitimate KDE developer
arojaswere forged using git commit metadata signature manipulation to disguise ownership takeovers [Sources 3 and 6]. - confirmed: Detailed reverse engineering from ioctl.fail mapped the Rust credential-stealing payload (
deps), C2 exfiltration routes over Tor onion domains (olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion), and the exact directory locations of the eBPF rootkit maps [Source 2]. - likely: The use of
/usr/bin/monero-wallet-guichecks inside the infostealer indicates the attackers planned to deploy cryptominers or steal Monero wallet data from targeted developer workstations [Source 2]. - not_observed: No compromises of upstream official Arch Linux software repositories (
[core],[extra]) or official package databases were identified during this campaign [Sources 1 and 4].
Impact Determination
| Classification | Criteria | Required evidence | Handling decision | Closure condition |
|---|---|---|---|---|
| Confirmed compromise | An affected AUR package was installed and execution of npm or bun malicious sub-packages is logged or eBPF maps exist. | systemd unit files, /sys/fs/bpf/hidden_* existence, or deps execution logs. | Isolate the host, boot from clean media to verify filesystem, rotate all keys, and regenerate SSH identities. | Clean OS install verified, all passwords changed, SSH keys rotated. |
| Presumed exposed | Compromised AUR package name and version installed within the June 9–12 window, but execution logs are unavailable. | pacman transaction logs (pacman.log), ~/.npm/_cacache/ or ~/.bun/ cache directories. | Assume all secrets accessible on the developer endpoint have been exfiltrated. Revoke API tokens immediately. | Dependency verification clean, credential rotation complete. |
| Potentially exposed | Listed AUR packages exist in system but timestamps do not match the campaign window. | Package installation metadata from pacman -Qi. | Check PKGBUILD diff history for unexpected npm/bun executions. | Audit confirmation showing no malicious hook installation. |
| Not exposed | No compromised packages installed, and local caches are clean. | Negative results on scanner script run. | No action required. Document negative result. | Case file closure. |
| Unknown | System logs or developer workstations are missing local telemetry. | Gaps in endpoint logs. | Execute proactive rotation of high-value credentials that had write access from this endpoint. | Telemetry gaps resolved or credentials fully rotated. |
Minimum Evidence To Collect
package_evidence:
- "Log entries of pacman installs for alvr, guiscrcpy, netmon-git, inadyn-mt, nodejs-elm, keepassx2"
- "Presence of atomic-lockfile or js-digest in npm/bun lockfiles or cache folders"
execution_evidence:
- "Preinstall scripts running ./src/hooks/deps"
- "Execution of js-digest or atomic-lockfile packages during package compilation"
network_evidence:
- "Network requests to temp.sh/upload"
- "SOCKS-style Tor proxy network connections on TCP/80 or TCP/8080"
- "Connection to olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion"
runtime_evidence:
- "eBPF maps: /sys/fs/bpf/hidden_pids, /sys/fs/bpf/hidden_names, /sys/fs/bpf/hidden_inodes"
- "Unknown systemd service units with Restart=always and RestartSec=30"
Timeline
- 2026-06-09T00:00:00Z: Attackers begin pushing forged commits to adopt orphaned packages. The account
krisztinavargapushes the malicious update to thealvrpackage [Sources 1, 3]. - 2026-06-11T12:00:00Z: Initial reports of anomalous dependencies appear on the Arch General mailing list [Source 3].
- 2026-06-11T18:00:00Z: Security analysis site ioctl.fail publishes a preliminary teardown of the Rust stealer and eBPF hook behaviors [Source 2].
- 2026-06-12T08:00:00Z: Attackers shift tactics to Bun-based
js-digestwave using accountscustodiatovarandveramagalhaes[Sources 1, 4]. - 2026-06-12T14:00:00Z: Legitimate maintainers verify forged commit author metadata impersonating
arojas[Source 6]. - 2026-06-12T18:00:00Z: AUR admins purge the compromised packages and accounts. Community detection repositories go live [Sources 1, 4].
What Happened
The attack utilized the trusted nature of community-built AUR helper environments. When an orphaned package is adopted on the AUR, there is no automatic warning sent to existing users. The attackers took over these packages and updated the PKGBUILDs. By configuring the installation lifecycle (via preinstall or .install hooks) to run npm install atomic-lockfile or bun install js-digest, they forced package managers to download external javascript dependencies during compilation [Sources 1 and 2].
The javascript dependencies contained a preinstall script that executed a native binary named deps [Source 2]. This binary compiled in Rust searched for credential stores and exfiltrated them to temp.sh or a Tor hidden service [Source 2]. If elevated system administration capabilities (such as CAP_BPF or CAP_SYS_ADMIN) were available, the payload mounted /sys/fs/bpf and pinned eBPF maps to hook processes, filesystem directories, and network connections, making the intrusion invisible to local system administrators [Sources 2 and 4].
Technical Analysis
Package Manipulation
package_identity:
registry: "aur"
malicious_packages:
- "alvr"
- "guiscrcpy"
- "netmon-git"
- "inadyn-mt"
- "nodejs-elm"
- "keepassx2"
injected_commands:
- "npm install atomic-lockfile"
- "bun install js-digest"
Execution And Collection
The execution chain is triggered by running makepkg or an AUR helper like yay. The installer invokes npm or bun, which runs the pre-install script ./src/hooks/deps. Once active, the Rust binary collects:
- Discord, Telegram, and Slack session tokens.
- Personal Access Tokens and SSH keys for Git.
- Credentials stored in web browser cookies and sqlite profiles.
- AWS, GCP, and Docker credentials.
- Filesystem paths containing crypto wallets.
Exfiltration
primary_exfiltration:
onion_address: "olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion"
proxy_mechanism: "bundled local Tor SOCKS proxy transport"
fallback_upload_url: "hxxps://temp[.]sh/upload"
Affected Assets and Blast Radius
affected_assets:
ecosystems:
- "Arch User Repository (AUR)"
- "npm"
- "bun"
targeted_environments:
- "Arch Linux developer workstations using AUR packages"
- "CI/CD systems running makepkg tasks"
compromised_identities:
- "Developer GitHub/npm sessions"
- "SSH private keys"
- "Cryptocurrency wallets"
Compromised AUR Packages
Below is a consolidated list of the known compromised AUR packages hijacked during the Atomic Arch campaign. If any of these packages are installed on your system or present in your package build cache (e.g. ~/.cache/yay/ or ~/.cache/paru/), check their git logs and look for unexpected npm or bun dependency commands inside the PKGBUILD file.
Show all 512 compromised AUR packages
123pan-bin1code8192eu-dkms-gitactual-aiadblock2privoxyaion-gitalbion-online-launcher-binalienfxalvrandroid-signapkandroid-signapk-guiandroid-support-repositoryannobinansible-language-serverantfs-cli-gitanythingllm-appimageanythingllm-cli-binapk-installer-guiapm_planner-binapothemapple-music-desktoparch-update-vaiarchjharchlinux-themes-slimarchmagearchtex-gitarm-linux-gnueabihf-binutilsartanis-gitastro-editor-appimageatolm-openbox-themeautohand-cliautolabelautologinawesome-revelation-gitazurlaneautoscriptbcachefs-kernel-dkms-gitbeebeepbitcoin-core-gitblinkenlibblueproximity-py3-gitbooklorebrow6elbrow6el-gitcanon-pixma-mg3000-complete-fixedcartridge-cliccase-binccl-gitcgminercharcoalcinny-desktop-system-trayclaiclang19clash-micling-gitcmuclmtkcnijfilter-commoncodenomad-bincodeql-cli-bincogpit-bincolorhug-clientcolorzcompiler-rt19compizconfig-pythoncoolreadercowdancercutefish-calculatorcutefish-corecutefish-dockcutefish-filemanagercutefish-iconscutefish-launchercutefish-qt-pluginscutefish-screenlockercutefish-screenshotcutefish-settingscutefish-statusbarcutefish-wallpaperscvs-feature-bincynthiune.appdagu-bindatatype99deheaderdepdh-pythondifidifi-bindoctocdots-hyprland-fork-gitdvdripdyad-bineasy_spiceedconv-binefiboots-giteislelectrum-nmcelmerfemepson-inkjet-printer-escpr2-clos-binexodus-wallet-binexoduswalletfarmmod-hubfastjetfastoggencfatxfcitx5-pinyin-sougou-dict-gitffmpeg-bitrate-statsffmpeg-quality-metricsfindpkg-gitfirefox-extension-adnauseam-bin-amofirmium-desktop-gitfishuifishui-gitflashfocusflexiblasflynarwhalfmlibforgecode-binformidable-binframefrutoolftlfuthark-bingdlgdlmmgit-annex-standalonegnome-contacts-gitgnome-randr-rustgnutls3.8.9gopher2600gopher2600-bingoshgpx-viewergravemangreen-tunnel-bingreetd-wlgreet-gitgtkimageviewguile-readerguiscrcpygummygummy-githackmatrix-githarmony-wadheadphoneshearthstone-linux-gui-appimagehearthstone-linux-gui-binhepmc2hister-githnswlib-githorsthydownloader-githydrus-giti3bar-riverianny-binibm-sw-tpm2ihaskell-gitimageglassinadyninadyn-mtindicator-sessioninfnoise-openssl-gitinterface99ios-webkit-debug-proxyipfs-desktop-binipswiron-heart-gitjasp-desktopjd-guik3supkdbkddockwidgets-gitkeepassx2kexikisskookbookkpropertykreportktealatex-digsiglazylpsolverlibs-gitledger-udev-binlesstiflib32-egl-waylandlibafterimagelibbobcatlibcutefishlibffi-staticlibgdatalibjxl-noglycinlibquvilibquvi-scriptslibretro-hatari-enhanced-gitlibxdifflibxml-rubylibyamilinux-cachyos-deckify-nativelinux-cachyos-deckify-native-headerslinux-cachyos-nativelinux-cachyos-native-headerslinux-cachyos-native-nvidia-openlinux-cachyos-rc-nativelinux-cachyos-rc-native-headerslinux-cachyos-rc-native-nvidia-openlinux-toolliri-cmake-shared-gitlitelllllvm-cbe-gitlowfi-binls++lucidvideom5rcodemagpie-wmmako-center-gitmanuskriptmaszyna-gitmathsat-5matrixbrandymcp-probemcpatchermermaid-ascii-gitmermark-editormesa-dlss-reflex-gitmeteomimic-node-gitmingw-w64-geosmingw-w64-libsndfileminimax-bin-hardenedminitubemisuzu-music-binmono-addinsmonochromemonochrome-gitmoor-gitmopenmount-gtkn1-translatornaemonnaemon-livestatusnatappnebuchadnezzar-gitnemerleneovim-autopairs-gitneovim-nvim-treesitternerf-pinetmon-gitneuro-karaoke-wrapper-gitnew-api-privacy-filternew-api-privacy-filter-gitnextcloud-app-audioplayernextcloud-app-facerecognitionnextcloud-app-gpoddersyncnextcloud-app-integration-googlenextcloud-app-repodnextcloud-app-twofactor-gatewaynextcloud-gitnexus-binnginx-mod-vtsnhentai-gitnixnote2nocodbnoctyra-dotfiles-gitnoctyra-meta-gitnodejs-elmnotepad---binnox-binnrpenwchemnwchem-binob-xdocaml-lambda-termoctocodeoh-my-gitopencode-codebase-index-binopencorsairlink-gitopenui5opl-synthoptimizevideo-gitoracle-binovraspacforgepaper-desktop-binpaq8oparallel-pythonpass-clipelican-gitpenguin-subtitle-playerperl-gtk2-ex-podviewerperl-proc-parallelloopperl-set-objectperl-term-extendedcolorpg2ipset-gitphonon-qt5-vlcphp-blackfirephp-geoipphp-legacy-memcachephp-memcachephp-openswoole-gitphp-xdiffpicom-ftlabs-gitpicpuzpidgin-kwalletpidgin-nudge-svnpipetoyspipewire-visualizer-gitplex-media-player-customplex-media-player-modplex-media-player-v2premake-gitprisma4postgres-binprofile-sync-daemon-zenpymacspypiserverpypy-setuptoolspython-affinepython-aptpython-argdispatchpython-awkwardpython-axolotl-gitpython-calmjspython-celerypython-cerealizerpython-ci-infopython-coolnamepython-cu2qu-gitpython-datapropertypython-dbapi-compliancepython-dictobjectpython-dj-database-urlpython-django-modelclusterpython-django-rest-knoxpython-fastmcp-slimpython-finnhub-pythonpython-firebase-adminpython-fmu_manipulation_toolboxpython-futurepython-g4fpython-histpython-histoprintpython-hsaudiotag3kpython-iminuitpython-iso3166python-isr-gitpython-jsminpython-json2xmlpython-luckydonald-utilspython-milvus-lite-binpython-mmcifpython-monotonicpython-mplheppython-mplhep_datapython-netaudio-gitpython-netaudio-libpython-newspaper4kpython-nipypepython-nodejs-wheelpython-openai-harmonypython-orangepython-pdf2docxpython-piecashpython-pluginmgrpython-poetry-plugin-dotenvpython-privy-gitpython-pushbullet.pypython-pychromecast-gitpython-pylsp-ropepython-pymilvuspython-pysocks-gitpython-rembgpython-scikit-hep-testdatapython-sklearn-pandaspython-sqliteschemapython-starlette-compresspython-starsessionspython-steamcontroller-gitpython-tabledatapython-tarantoolpython-tradingeconomicspython-uhipython-uprootpython-vectorpython-xtarfilepython2-appdirspython2-fusepypython2-lazr-uripython2-mutagenpython2-notifypython2-packagingpython2-paverpython2-pyparsingpython2-simplejsonpython2-simpleparsepython2-stomperpython2-twodict-gitpython2-xlibqhttpengineqlementineqmdnsengineqnapiqobuz-player-binqtum-corequickswitch-i3r-dbplyrreactphysics3drepoporgeretibbs-client-gitrhythmbox-gitrimworldrog-helper-gitros2-humble-nav2-msgsrtorrent-psrtspeccy-gitruah-orchruby-exconruby-kramdown-rfc2629ruby-selenium-webdriverrunescape-launchersakura-launcher-guisandlockscreenpipe-binsdcc-binseahorse-nautilussentryshhmsgshhoptslipnetslipnet-binsmenusmenu-gitsmolrtspsmolrtsp-libeventsnry-shell-qssoapyptezukasolara-kernel-headerssonosanosope2soundpaad-binsshuttleesshuttlee-binstompbox-jack-gitstripe-clistylelint-config-recommendedsubbrutesublist3r-gitsubprocesssubsyncsvusway-xkb-switchertacktarantooltesseract-guithunar-nextcloud-pluginthunderbird-conversationstinyemutlpui-gittorch7-gittouchhletouchosc-bintranscreentsmttf-material-design-icons-gittunacode-clityping-game-cliucsf-chimeraukui-notification-daemonuplink-hibusbmountvapoursynth-preview-gitvbam-gitverso-gitvidcuttervim-easymotionvim-gitguttervim-indent-objectvim-molokaivim-pythonhelpervim-solidityvim-vitalvocalinux-gitvoquill-gpuwallpaper-generator-nextwayland-staticwe-layerd-gitwebilder-gtk-patchedwhatsie-gitwhisper2trwhisper2tr-gitwindowmaker-gitwindows2usb-gitwine-ninewire-desktopword-snatchers-cliworkbenchworkbuddy-binwrystr-gitwsjtx-betaxf86-input-joystickxf86-input-mtrack-gitxorg-xfsinfoxplotxpra-html5xray-domain-list-communityxsvgyargyt6801-dkmsyyzathura-gruvbox-gitzerx-lab-dida-binzerx-lab-zed-nightly-binzing-17-binzing-21-binzing-8-binzinnia-pythonzsdx
Indicators of Compromise
package_versions:
- "[email protected]"
- "[email protected]"
files:
- "/sys/fs/bpf/hidden_pids"
- "/sys/fs/bpf/hidden_names"
- "/sys/fs/bpf/hidden_inodes"
- "/usr/bin/monero-wallet-gui"
- "~/.npm/_cacache/"
- "~/.bun/install/cache/"
- "deps"
- "src/hooks/deps"
domains:
- "olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion"
- "temp.sh"
urls:
- "olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/linux"
- "olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/sha256/linux"
- "https://temp.sh/upload"
hashes:
- "6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b"
- "7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316"
- "47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204"
Actionable SOC/IR Hunt Recipes
Workstation Shell Audit Triage
Use these commands on local developer hosts to verify system state:
# 1. Scan local package manager cache (e.g. yay or paru build directories)
grep -rnw ~/.cache/ -e "atomic-lockfile" -e "js-digest"
# 2. Audit running or persistent systemd units for restarting behavior
find /etc/systemd/system/ ~/.config/systemd/user/ -name "*.service" -type f | while read -r svc; do
if grep -q "Restart=always" "$svc" && grep -q "RestartSec=30" "$svc"; then
echo "[!] Suspicious unit detected: $svc"
cat "$svc"
fi
done
# 3. Check for pinned eBPF maps used by the rootkit
if [ -d /sys/fs/bpf ]; then
ls -la /sys/fs/bpf/hidden_pids /sys/fs/bpf/hidden_names /sys/fs/bpf/hidden_inodes 2>/dev/null
fi
KQL Query
Deploy this query in SIEM/log analysis interfaces looking at process execution history:
// Query for npm or bun executing malicious packages
DeviceProcessEvents
| where Timestamp >= datetime(2026-06-09T00:00:00Z) and Timestamp <= datetime(2026-06-13T00:00:00Z)
| where ProcessCommandLine has_any ("atomic-lockfile", "js-digest")
or (ProcessCommandLine has "npm" and ProcessCommandLine has "install")
or (ProcessCommandLine has "bun" and ProcessCommandLine has "install")
Sigma Rule
For enterprise endpoint detection of process creation anomalies:
title: Potential Atomic Arch AUR Compromise Execution
id: 54930fae-e2b2-4d2c-8854-3c8121665a36
status: experimental
description: Detects execution patterns associated with the June 2026 AUR helper compromise campaign, where packages ran 'npm install atomic-lockfile' or 'bun install js-digest'.
references:
- https://haltingproblems.com/analysis/atomic-arch-aur-compromise/
author: Halting Problems
date: 2026-06-12
logsource:
category: process_creation
product: linux
detection:
selection_cmd:
CommandLine|contains:
- 'atomic-lockfile'
- 'js-digest'
selection_npm_bun:
CommandLine|contains|all:
- 'install'
CommandLine|contains:
- 'npm'
- 'bun'
condition: selection_cmd or selection_npm_bun
falsepositives:
- Administrative package manager debugging
level: high
Detection and Hunting
Script: local repository and exported telemetry scope
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from pathlib import Path
ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
LOG_ROOT = os.environ.get("LOG_ROOT", "")
OUT = Path(os.environ.get("OUT", "hp-atomic-arch-aur-compromise-scope"))
SINCE = "2026-06-09T00:00:00Z"
UNTIL = "2026-06-13T00:00:00Z"
PACKAGES = [
"atomic-lockfile",
"js-digest",
]
VERSIONS = [
"[email protected]",
"[email protected]",
]
FILES = [
"/sys/fs/bpf/hidden_pids",
"/sys/fs/bpf/hidden_names",
"/sys/fs/bpf/hidden_inodes",
"/usr/bin/monero-wallet-gui",
"~/.npm/_cacache/",
"~/.bun/install/cache/",
"deps",
"src/hooks/deps",
]
DOMAINS = [
"olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion",
"temp.sh",
]
URLS = [
"olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/linux",
"olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/sha256/linux",
"https://temp.sh/upload",
]
IPS = [
]
HASHES = [
"6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b",
"7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316",
"47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204",
]
PROCESS_PATTERNS = [
]
NETWORK_PATTERNS = [
]
# Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
# Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
OUT.mkdir(parents=True, exist_ok=True)
indicators_file = OUT / "indicators.txt"
# Collect unique indicators
indicators = set()
for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
for val in group:
if val:
indicators.add(val)
with open(indicators_file, "w") as f:
for ind in sorted(indicators):
f.write(ind + "\n")
print(f"[+] Written unique selectors to {indicators_file}")
# Walk local directory
print(f"[+] Scanning directory: {ROOT} for selectors...")
matches = []
exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
for root, dirs, filenames in os.walk(ROOT):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if matches:
(OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
print(f"[!] Found {len(matches)} matches in codebase!")
# Optional Log Scanning
if LOG_ROOT and os.path.exists(LOG_ROOT):
print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
log_matches = []
for root, _, filenames in os.walk(LOG_ROOT):
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
log_matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if log_matches:
(OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
print(f"[!] Found {len(log_matches)} matches in logs!")
if PACKAGES:
registry_dir = OUT / "registry"
registry_dir.mkdir(exist_ok=True)
for package in PACKAGES:
if not package: continue
safe_name = package.replace("/", "__")
print(f"[+] Querying npm view for {package}...")
res = subprocess.run(["npm", "view", package, "name", "version", "time", "versions", "dist-tags", "maintainers", "dist.tarball", "dist.integrity", "scripts", "--json"], capture_output=True, text=True)
if res.returncode == 0:
(registry_dir / f"npm-{safe_name}.json").write_text(res.stdout)
print(f"[+] Wrote scope artifacts under {OUT}")
Downstream Abuse Audits
Script: GitHub organization run, release, secret, and workflow audit
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from pathlib import Path
if "ORG" not in os.environ:
print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
sys.exit(1)
ORG = os.environ["ORG"]
SINCE = "2026-06-09T00:00:00Z"
UNTIL = "2026-06-13T00:00:00Z"
OUT = Path(os.environ.get("OUT", "hp-atomic-arch-aur-compromise-github-audit"))
SELECTORS = [
"atomic-lockfile",
"js-digest",
"[email protected]",
"[email protected]",
"/sys/fs/bpf/hidden_pids",
"/sys/fs/bpf/hidden_names",
"/sys/fs/bpf/hidden_inodes",
"/usr/bin/monero-wallet-gui",
"~/.npm/_cacache/",
"~/.bun/install/cache/",
"deps",
"src/hooks/deps",
"olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion",
"temp.sh",
"olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/linux",
"olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/sha256/linux",
"https://temp.sh/upload",
"6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b",
"7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316",
"47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204",
]
# Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
# Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "runs").mkdir(exist_ok=True)
(OUT / "logs").mkdir(exist_ok=True)
(OUT / "repos").mkdir(exist_ok=True)
# 1. Write incident-selectors file
selectors_file = OUT / "incident-selectors.txt"
with open(selectors_file, "w") as sf:
for s in SELECTORS:
if s:
sf.write(s + "\n")
# 2. Get list of repos
print(f"[+] Fetching repositories for organization: {ORG}")
repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
if repo_res.returncode != 0:
print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
sys.exit(1)
repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
for repo in repos:
safe_repo = repo.replace("/", "__")
print(f"[+] Auditing repository: {repo}")
# Check runs in the window
runs_res = subprocess.run([
"gh", "api", f"/repos/{repo}/actions/runs",
"-f", "per_page=100",
"-f", f"created=>={SINCE}",
"--paginate"
], capture_output=True, text=True)
if runs_res.returncode == 0:
try:
all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
if filtered_runs:
with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
for run in filtered_runs:
rf.write(json.dumps(run) + "\n")
# Fetch log dynamically
run_id = str(run["id"])
log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
if log_res.returncode == 0:
(OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
# Fetch details
view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
if view_res.returncode == 0:
(OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
except Exception as e:
print(f"[-] Error parsing runs for {repo}: {e}")
# Check releases in window
subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
# Check repo secrets updated in window
subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
# Check deploy keys
subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
# Scan output directory for any indicator selector matches
print("[+] Scanning gathered telemetry for indicator matches...")
subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
print(f"[+] Wrote GitHub audit artifacts under {OUT}")
Script: cloud OIDC and deployment credential follow-on audit
#!/usr/bin/env python3
import os
import json
import subprocess
from pathlib import Path
SINCE = "2026-06-09T00:00:00Z"
UNTIL = "2026-06-13T00:00:00Z"
OUT = Path(os.environ.get("OUT", "hp-atomic-arch-aur-compromise-cloud-audit"))
AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
# Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
# Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
OUT.mkdir(parents=True, exist_ok=True)
# 1. AWS CloudTrail Audit
print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
aws_events = []
for region in AWS_REGIONS:
res = subprocess.run([
"aws", "cloudtrail", "lookup-events",
"--region", region,
"--start-time", SINCE,
"--end-time", UNTIL,
"--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
"--output", "json"
], capture_output=True, text=True)
if res.returncode == 0:
try:
events = json.loads(res.stdout).get("Events", [])
for event in events:
ct = json.loads(event.get("CloudTrailEvent", "{}"))
ct["region"] = region
aws_events.append(ct)
except Exception as e:
print(f"[-] Error parsing AWS CloudTrail events: {e}")
if aws_events:
with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
for ev in aws_events:
f.write(json.dumps(ev) + "\n")
# Audit follow-on events for returned access keys
for ev in aws_events:
access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
region = ev.get("region", "us-east-1")
if access_key:
print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
f_res = subprocess.run([
"aws", "cloudtrail", "lookup-events",
"--region", region,
"--start-time", SINCE,
"--end-time", UNTIL,
"--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
"--output", "json"
], capture_output=True, text=True)
if f_res.returncode == 0:
try:
f_events = json.loads(f_res.stdout).get("Events", [])
with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
for fe in f_events:
ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
except Exception as e:
print(f"[-] Error writing follow-on events: {e}")
# 2. Azure Activity Log Audit
print("[+] Querying Azure activity logs...")
az_res = subprocess.run([
"az", "monitor", "activity-log", "list",
"--start-time", SINCE,
"--end-time", UNTIL,
"--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
"-o", "json"
], capture_output=True, text=True)
if az_res.returncode == 0:
(OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
# 3. GCP Logging Audit
print("[+] Querying GCP Cloud Logging...")
gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
gcp_res = subprocess.run([
"gcloud", "logging", "read", gcp_filter,
"--format", "json"
], capture_output=True, text=True)
if gcp_res.returncode == 0:
(OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
print(f"[+] Wrote cloud audit artifacts under {OUT}")
Script: registry metadata and artifact audit
#!/usr/bin/env python3
import os
import json
import subprocess
from pathlib import Path
SINCE = "2026-06-09T00:00:00Z"
OUT = Path(os.environ.get("OUT", "hp-atomic-arch-aur-compromise-registry-audit"))
PACKAGES = [
"atomic-lockfile",
"js-digest",
]
VERSIONS = [
"[email protected]",
"[email protected]",
]
# Positive signal: registry metadata, package tarballs, or cached artifacts contain the exact affected package/version values.
# Remediation trigger: any internal package cache, build artifact, or deployment using these package/version values requires exposure scoping.
OUT.mkdir(parents=True, exist_ok=True)
with open(OUT / "affected-versions.txt", "w") as av:
for version in VERSIONS:
if version:
av.write(version + "\n")
# 1. Audit npm dependencies in lockfiles/package.json
print("[+] Scanning lockfiles for npm selectors...")
for file in ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "package.json"]:
if Path(file).exists():
subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), file])
# 2. Query registry metadata and fetch tarballs for local analysis
metadata_dir = OUT / "metadata"
tarballs_dir = OUT / "tarballs"
metadata_dir.mkdir(exist_ok=True)
tarballs_dir.mkdir(exist_ok=True)
for package in PACKAGES:
if not package: continue
safe_name = package.replace("/", "__")
print(f"[+] Querying npm view for {package}...")
res = subprocess.run(["npm", "view", package, "time", "versions", "dist-tags", "maintainers", "dist.tarball", "dist.integrity", "scripts", "--json"], capture_output=True, text=True)
if res.returncode == 0:
(metadata_dir / f"npm-{safe_name}.json").write_text(res.stdout)
subprocess.run(["npm", "pack", package, "--pack-destination", str(tarballs_dir)], capture_output=True)
# 3. HOW TO REVOKE AND ROTATE EXPOSED NPM PUBLISHING TOKENS:
# Revoke all compromised tokens via npm CLI:
# subprocess.run(["npm", "token", "list"])
# subprocess.run(["npm", "token", "revoke", "123456"])
# Or logout to revoke the current session:
# subprocess.run(["npm", "logout"])
# Generate a new publishing token with MFA protection:
# subprocess.run(["npm", "token", "create", "--read-only=false", "--cidr=0.0.0.0/0"])
print(f"[+] Wrote registry audit artifacts under {OUT}")
Sources
- IFIN Discourse: 400 AUR Packages Compromised with Infostealer and Rootkit - Role: PRIMARY_RESEARCH - Impact: Identification of the two waves (npm/atomic-lockfile and bun/js-digest) and initial hashes.
- ioctl.fail: Preliminary Analysis of AUR Malware - Role: PRIMARY_RESEARCH - Impact: Detailed teardown of the Rust stealer, C2 Tor configurations, and eBPF rootkit mechanism.
- Arch Linux aur-general General Discussion List Archive - Role: DIRECT_SOURCE - Impact: Community lists of compromised packages and initial user bug reports.
- lenucksi/aur-malware-check GitHub Repository - Role: COMMUNITY_DETECTION - Impact: Reference detection scripts, package checklists, and remediation instructions.
- Socket.dev: atomic-lockfile npm package - Role: REGISTRY_METADATA - Impact: NPM telemetry, publisher statistics, and package removal state.
- Chaos.social Mastodon Post by David Runge - Role: DIRECT_SOURCE - Impact: Clarification that Arojas maintainer account commits were forged by the attacker.
IOC Clipboard
16 IOCsolrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion temp.sh temp[.]sh olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/linux olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion/bin/linux olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/sha256/linux olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion/bin/sha256/linux https://temp.sh/upload hxxps://temp[.]sh/upload 6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b 6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b 7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316 7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316 47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204 47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204 /sys/fs/bpf/hidden_pids /sys/fs/bpf/hidden_pids /sys/fs/bpf/hidden_names /sys/fs/bpf/hidden_names /sys/fs/bpf/hidden_inodes /sys/fs/bpf/hidden_inodes /usr/bin/monero-wallet-gui /usr/bin/monero-wallet-gui ~/.npm/_cacache/ ~/.npm/_cacache/ ~/.bun/install/cache/ ~/.bun/install/cache/ deps deps src/hooks/deps src/hooks/deps