critical Threat analysis

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.

#aur#arch#npm#bun#ebpf#rootkit#credential-theft#infostealer
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, and veramagalhaes [Sources 1, 3, and 4].
    • confirmed: Developer David Runge and community audits proved that commits referencing legitimate KDE developer arojas were 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-gui checks 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

    ClassificationCriteriaRequired evidenceHandling decisionClosure condition
    Confirmed compromiseAn 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 exposedCompromised 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 exposedListed 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 exposedNo compromised packages installed, and local caches are clean.Negative results on scanner script run.No action required. Document negative result.Case file closure.
    UnknownSystem 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 krisztinavarga pushes the malicious update to the alvr package [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-digest wave using accounts custodiatovar and veramagalhaes [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-bin
    • 1code
    • 8192eu-dkms-git
    • actual-ai
    • adblock2privoxy
    • aion-git
    • albion-online-launcher-bin
    • alienfx
    • alvr
    • android-signapk
    • android-signapk-gui
    • android-support-repository
    • annobin
    • ansible-language-server
    • antfs-cli-git
    • anythingllm-appimage
    • anythingllm-cli-bin
    • apk-installer-gui
    • apm_planner-bin
    • apothem
    • apple-music-desktop
    • arch-update-vai
    • archjh
    • archlinux-themes-slim
    • archmage
    • archtex-git
    • arm-linux-gnueabihf-binutils
    • artanis-git
    • astro-editor-appimage
    • atolm-openbox-theme
    • autohand-cli
    • autolabel
    • autologin
    • awesome-revelation-git
    • azurlaneautoscript
    • bcachefs-kernel-dkms-git
    • beebeep
    • bitcoin-core-git
    • blinkenlib
    • blueproximity-py3-git
    • booklore
    • brow6el
    • brow6el-git
    • canon-pixma-mg3000-complete-fixed
    • cartridge-cli
    • ccase-bin
    • ccl-git
    • cgminer
    • charcoal
    • cinny-desktop-system-tray
    • clai
    • clang19
    • clash-mi
    • cling-git
    • cmuclmtk
    • cnijfilter-common
    • codenomad-bin
    • codeql-cli-bin
    • cogpit-bin
    • colorhug-client
    • colorz
    • compiler-rt19
    • compizconfig-python
    • coolreader
    • cowdancer
    • cutefish-calculator
    • cutefish-core
    • cutefish-dock
    • cutefish-filemanager
    • cutefish-icons
    • cutefish-launcher
    • cutefish-qt-plugins
    • cutefish-screenlocker
    • cutefish-screenshot
    • cutefish-settings
    • cutefish-statusbar
    • cutefish-wallpapers
    • cvs-feature-bin
    • cynthiune.app
    • dagu-bin
    • datatype99
    • deheader
    • dep
    • dh-python
    • difi
    • difi-bin
    • doctoc
    • dots-hyprland-fork-git
    • dvdrip
    • dyad-bin
    • easy_spice
    • edconv-bin
    • efiboots-git
    • eisl
    • electrum-nmc
    • elmerfem
    • epson-inkjet-printer-escpr2-clos-bin
    • exodus-wallet-bin
    • exoduswallet
    • farmmod-hub
    • fastjet
    • fastoggenc
    • fatx
    • fcitx5-pinyin-sougou-dict-git
    • ffmpeg-bitrate-stats
    • ffmpeg-quality-metrics
    • findpkg-git
    • firefox-extension-adnauseam-bin-amo
    • firmium-desktop-git
    • fishui
    • fishui-git
    • flashfocus
    • flexiblas
    • flynarwhal
    • fmlib
    • forgecode-bin
    • formidable-bin
    • frame
    • frutool
    • ftl
    • futhark-bin
    • gdl
    • gdlmm
    • git-annex-standalone
    • gnome-contacts-git
    • gnome-randr-rust
    • gnutls3.8.9
    • gopher2600
    • gopher2600-bin
    • gosh
    • gpx-viewer
    • graveman
    • green-tunnel-bin
    • greetd-wlgreet-git
    • gtkimageview
    • guile-reader
    • guiscrcpy
    • gummy
    • gummy-git
    • hackmatrix-git
    • harmony-wad
    • headphones
    • hearthstone-linux-gui-appimage
    • hearthstone-linux-gui-bin
    • hepmc2
    • hister-git
    • hnswlib-git
    • horst
    • hydownloader-git
    • hydrus-git
    • i3bar-river
    • ianny-bin
    • ibm-sw-tpm2
    • ihaskell-git
    • imageglass
    • inadyn
    • inadyn-mt
    • indicator-session
    • infnoise-openssl-git
    • interface99
    • ios-webkit-debug-proxy
    • ipfs-desktop-bin
    • ipsw
    • iron-heart-git
    • jasp-desktop
    • jd-gui
    • k3sup
    • kdb
    • kddockwidgets-git
    • keepassx2
    • kexi
    • kiss
    • kookbook
    • kproperty
    • kreport
    • ktea
    • latex-digsig
    • lazylpsolverlibs-git
    • ledger-udev-bin
    • lesstif
    • lib32-egl-wayland
    • libafterimage
    • libbobcat
    • libcutefish
    • libffi-static
    • libgdata
    • libjxl-noglycin
    • libquvi
    • libquvi-scripts
    • libretro-hatari-enhanced-git
    • libxdiff
    • libxml-ruby
    • libyami
    • linux-cachyos-deckify-native
    • linux-cachyos-deckify-native-headers
    • linux-cachyos-native
    • linux-cachyos-native-headers
    • linux-cachyos-native-nvidia-open
    • linux-cachyos-rc-native
    • linux-cachyos-rc-native-headers
    • linux-cachyos-rc-native-nvidia-open
    • linux-tool
    • liri-cmake-shared-git
    • lite
    • lll
    • llvm-cbe-git
    • lowfi-bin
    • ls++
    • lucidvideo
    • m5rcode
    • magpie-wm
    • mako-center-git
    • manuskript
    • maszyna-git
    • mathsat-5
    • matrixbrandy
    • mcp-probe
    • mcpatcher
    • mermaid-ascii-git
    • mermark-editor
    • mesa-dlss-reflex-git
    • meteo
    • mimic-node-git
    • mingw-w64-geos
    • mingw-w64-libsndfile
    • minimax-bin-hardened
    • minitube
    • misuzu-music-bin
    • mono-addins
    • monochrome
    • monochrome-git
    • moor-git
    • mopen
    • mount-gtk
    • n1-translator
    • naemon
    • naemon-livestatus
    • natapp
    • nebuchadnezzar-git
    • nemerle
    • neovim-autopairs-git
    • neovim-nvim-treesitter
    • nerf-pi
    • netmon-git
    • neuro-karaoke-wrapper-git
    • new-api-privacy-filter
    • new-api-privacy-filter-git
    • nextcloud-app-audioplayer
    • nextcloud-app-facerecognition
    • nextcloud-app-gpoddersync
    • nextcloud-app-integration-google
    • nextcloud-app-repod
    • nextcloud-app-twofactor-gateway
    • nextcloud-git
    • nexus-bin
    • nginx-mod-vts
    • nhentai-git
    • nixnote2
    • nocodb
    • noctyra-dotfiles-git
    • noctyra-meta-git
    • nodejs-elm
    • notepad---bin
    • nox-bin
    • nrpe
    • nwchem
    • nwchem-bin
    • ob-xd
    • ocaml-lambda-term
    • octocode
    • oh-my-git
    • opencode-codebase-index-bin
    • opencorsairlink-git
    • openui5
    • opl-synth
    • optimizevideo-git
    • oracle-bin
    • ovras
    • pacforge
    • paper-desktop-bin
    • paq8o
    • parallel-python
    • pass-cli
    • pelican-git
    • penguin-subtitle-player
    • perl-gtk2-ex-podviewer
    • perl-proc-parallelloop
    • perl-set-object
    • perl-term-extendedcolor
    • pg2ipset-git
    • phonon-qt5-vlc
    • php-blackfire
    • php-geoip
    • php-legacy-memcache
    • php-memcache
    • php-openswoole-git
    • php-xdiff
    • picom-ftlabs-git
    • picpuz
    • pidgin-kwallet
    • pidgin-nudge-svn
    • pipetoys
    • pipewire-visualizer-git
    • plex-media-player-custom
    • plex-media-player-mod
    • plex-media-player-v2
    • premake-git
    • prisma4postgres-bin
    • profile-sync-daemon-zen
    • pymacs
    • pypiserver
    • pypy-setuptools
    • python-affine
    • python-apt
    • python-argdispatch
    • python-awkward
    • python-axolotl-git
    • python-calmjs
    • python-celery
    • python-cerealizer
    • python-ci-info
    • python-coolname
    • python-cu2qu-git
    • python-dataproperty
    • python-dbapi-compliance
    • python-dictobject
    • python-dj-database-url
    • python-django-modelcluster
    • python-django-rest-knox
    • python-fastmcp-slim
    • python-finnhub-python
    • python-firebase-admin
    • python-fmu_manipulation_toolbox
    • python-future
    • python-g4f
    • python-hist
    • python-histoprint
    • python-hsaudiotag3k
    • python-iminuit
    • python-iso3166
    • python-isr-git
    • python-jsmin
    • python-json2xml
    • python-luckydonald-utils
    • python-milvus-lite-bin
    • python-mmcif
    • python-monotonic
    • python-mplhep
    • python-mplhep_data
    • python-netaudio-git
    • python-netaudio-lib
    • python-newspaper4k
    • python-nipype
    • python-nodejs-wheel
    • python-openai-harmony
    • python-orange
    • python-pdf2docx
    • python-piecash
    • python-pluginmgr
    • python-poetry-plugin-dotenv
    • python-privy-git
    • python-pushbullet.py
    • python-pychromecast-git
    • python-pylsp-rope
    • python-pymilvus
    • python-pysocks-git
    • python-rembg
    • python-scikit-hep-testdata
    • python-sklearn-pandas
    • python-sqliteschema
    • python-starlette-compress
    • python-starsessions
    • python-steamcontroller-git
    • python-tabledata
    • python-tarantool
    • python-tradingeconomics
    • python-uhi
    • python-uproot
    • python-vector
    • python-xtarfile
    • python2-appdirs
    • python2-fusepy
    • python2-lazr-uri
    • python2-mutagen
    • python2-notify
    • python2-packaging
    • python2-paver
    • python2-pyparsing
    • python2-simplejson
    • python2-simpleparse
    • python2-stomper
    • python2-twodict-git
    • python2-xlib
    • qhttpengine
    • qlementine
    • qmdnsengine
    • qnapi
    • qobuz-player-bin
    • qtum-core
    • quickswitch-i3
    • r-dbplyr
    • reactphysics3d
    • repoporge
    • retibbs-client-git
    • rhythmbox-git
    • rimworld
    • rog-helper-git
    • ros2-humble-nav2-msgs
    • rtorrent-ps
    • rtspeccy-git
    • ruah-orch
    • ruby-excon
    • ruby-kramdown-rfc2629
    • ruby-selenium-webdriver
    • runescape-launcher
    • sakura-launcher-gui
    • sandlock
    • screenpipe-bin
    • sdcc-bin
    • seahorse-nautilus
    • sentry
    • shhmsg
    • shhopt
    • slipnet
    • slipnet-bin
    • smenu
    • smenu-git
    • smolrtsp
    • smolrtsp-libevent
    • snry-shell-qs
    • soapyptezuka
    • solara-kernel-headers
    • sonosano
    • sope2
    • soundpaad-bin
    • sshuttlee
    • sshuttlee-bin
    • stompbox-jack-git
    • stripe-cli
    • stylelint-config-recommended
    • subbrute
    • sublist3r-git
    • subprocess
    • subsync
    • svu
    • sway-xkb-switcher
    • tack
    • tarantool
    • tesseract-gui
    • thunar-nextcloud-plugin
    • thunderbird-conversations
    • tinyemu
    • tlpui-git
    • torch7-git
    • touchhle
    • touchosc-bin
    • transcreen
    • tsm
    • ttf-material-design-icons-git
    • tunacode-cli
    • typing-game-cli
    • ucsf-chimera
    • ukui-notification-daemon
    • uplink-hib
    • usbmount
    • vapoursynth-preview-git
    • vbam-git
    • verso-git
    • vidcutter
    • vim-easymotion
    • vim-gitgutter
    • vim-indent-object
    • vim-molokai
    • vim-pythonhelper
    • vim-solidity
    • vim-vital
    • vocalinux-git
    • voquill-gpu
    • wallpaper-generator-next
    • wayland-static
    • we-layerd-git
    • webilder-gtk-patched
    • whatsie-git
    • whisper2tr
    • whisper2tr-git
    • windowmaker-git
    • windows2usb-git
    • wine-nine
    • wire-desktop
    • word-snatchers-cli
    • workbench
    • workbuddy-bin
    • wrystr-git
    • wsjtx-beta
    • xf86-input-joystick
    • xf86-input-mtrack-git
    • xorg-xfsinfo
    • xplot
    • xpra-html5
    • xray-domain-list-community
    • xsvg
    • yarg
    • yt6801-dkms
    • yy
    • zathura-gruvbox-git
    • zerx-lab-dida-bin
    • zerx-lab-zed-nightly-bin
    • zing-17-bin
    • zing-21-bin
    • zing-8-bin
    • zinnia-python
    • zsdx

    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

    1. 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.
    2. ioctl.fail: Preliminary Analysis of AUR Malware - Role: PRIMARY_RESEARCH - Impact: Detailed teardown of the Rust stealer, C2 Tor configurations, and eBPF rootkit mechanism.
    3. Arch Linux aur-general General Discussion List Archive - Role: DIRECT_SOURCE - Impact: Community lists of compromised packages and initial user bug reports.
    4. lenucksi/aur-malware-check GitHub Repository - Role: COMMUNITY_DETECTION - Impact: Reference detection scripts, package checklists, and remediation instructions.
    5. Socket.dev: atomic-lockfile npm package - Role: REGISTRY_METADATA - Impact: NPM telemetry, publisher statistics, and package removal state.
    6. 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 IOCs
    Defang IOCs
    domain olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion
    domain temp.sh temp[.]sh
    url olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/linux olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion/bin/linux
    url olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion/bin/sha256/linux olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid[.]onion/bin/sha256/linux
    url https://temp.sh/upload hxxps://temp[.]sh/upload
    hash 6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b 6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b
    hash 7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316 7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316
    hash 47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204 47893d9badc38c54b71321263ce8178c1abb10396e0aadf9793e61ec8829e204
    file /sys/fs/bpf/hidden_pids /sys/fs/bpf/hidden_pids
    file /sys/fs/bpf/hidden_names /sys/fs/bpf/hidden_names
    file /sys/fs/bpf/hidden_inodes /sys/fs/bpf/hidden_inodes
    file /usr/bin/monero-wallet-gui /usr/bin/monero-wallet-gui
    file ~/.npm/_cacache/ ~/.npm/_cacache/
    file ~/.bun/install/cache/ ~/.bun/install/cache/
    file deps deps
    file src/hooks/deps src/hooks/deps