跳到正文

lenucksi

aur-malware-check

Detection tools for the June 2026 atomic-lockfile AUR supply-chain attack. Consolidated from community Gists.

README 已保存到本站,可直接阅读

Documentation snapshot

README 快照

这篇是英文原文

下面正文是项目自己的英文 README。想读全文就用浏览器自带的整页翻译: Chrome / Edge 点地址栏右侧的翻译图标,或用右键菜单里的「翻译成中文」; 手机浏览器一般在菜单里。

本页保存的是公开项目资料快照,阅读过程不需要连接 GitHub。

AUR Malware Check — June 2026 Campaign & Cross-Campaign Detection

Detection and analysis tools for the atomic-lockfile supply-chain attack on the Arch User Repository (AUR), generalized to a campaign-based architecture that handles multiple concurrent and historical attack waves (CHAOS RAT 2025, Russian spam packages, and future campaigns declared via campaigns.json).

This is a collection of all the scattered resources, especially the ones in the detection scripts Gist - they made this, I just collected this to a repo so I have it all in one place and possibly people could put up PR’s instead of Gist links across multiple posts. Certainly see the source section for details on the sources!

[!TIP] Questions, support, or general discussion? Head over to Discussions. Issues are reserved for bug reports and feature requests only.

[!NOTE] This project is Python-first. The detection tool is the aur_check package (Python 3.14+, standard library only, typed, tested). The original bash scripts have been removed; their behaviour is fully covered by the Python implementation. See the source section for credits.

Aur-infected 06/2026 campaign 1600+ AUR packages compromised by attackers who injected npm install atomic-lockfile, bun install js-digest, or lockfile-js into PKGBUILD/install files. Two attack waves:

  1. atomic-lockfile / lockfile-js (npm) — accounts krisztinavarga, franziskaweber, tobiaswesterburg, ellenmyklebust; arojas (impersonated legitimate maintainer — see Impersonation Clarification)
  2. js-digest (bun) — accounts custodiatovar, veramagalhaes

Both deliver an infostealer and eBPF rootkit targeting developer credentials, browser data, and CI/CD secrets.

Quick Start

No installation, no dependencies — just Python 3.14+ and a checkout of this repo. Run the package directly with python -m aur_check:

# Check if you have any infected packages (all campaigns)
python -m aur_check

# Full scan with all optional checks (systemd, eBPF, npm + bun + yarn + pnpm cache)
python -m aur_check --full

# Refresh all campaign package lists from their upstream sources
python -m aur_check --refresh --full

# Output scan results as JSON
python -m aur_check --json

# List configured campaigns with their lists, windows, env vars, and refresh URLs
python -m aur_check --list-campaigns

# List campaigns in machine-readable JSON
python -m aur_check --list-campaigns --json

# Fetch latest campaigns.json from upstream, validate, show diff, write
python -m aur_check --refresh-campaigns

# Same, but only show diff without writing
python -m aur_check --refresh-campaigns --dry-run

# Add an extra package list to a specific campaign
python -m aur_check -l aur-infected=/path/to/extra_list.txt

# Check bun cache specifically (for js-digest / atomic-lockfile)
python -m aur_check --check-bun-cache

# Check yarn cache specifically (Yarn Classic v1 + Yarn Berry v2+)
python -m aur_check --check-yarn-cache

# Check pnpm store/cache specifically (global installs + metadata + dlx)
python -m aur_check --check-pnpm-cache

# Cross-campaign: scan all installed packages regardless of install date
python -m aur_check --all-time

# Scan every user's home (root only, opt-in)
sudo python -m aur_check --scan-all-homes --full

# CHAOS RAT (July 2025) packages are scanned automatically against their own
# window (2025-07-16..19). Override it via env vars if needed:
#   CHAOS_START_DATE=2025-07-15 CHAOS_END_DATE=2025-07-20 python -m aur_check

The date window and pacman log glob can be overridden via environment variables:

```bash
START_DATE=2026-06-09 END_DATE=2026-06-12 python -m aur_check
PACMAN_LOG_GLOB='/var/log/pacman.log*' python -m aur_check

What it checks

Check / FeatureDetailSource
Campaign-based architectureAll campaigns declared in data/campaigns.json with per-campaign lists, date windows, env overrides, and campaign_tag labelsOriginal addition
--list-campaignsShow configured campaigns with lists, windows, env vars, and refresh URLsOriginal addition
--refresh-campaignsFetch latest campaigns.json from upstream, validate, show diff, writeOriginal addition
--dry-runWith --refresh-campaigns, show diff without writingOriginal addition
--jsonMachine-readable output for --list-campaigns and scan resultsOriginal addition
-l [CAMPAIGN_ID=]PATHAdd an extra package list to a specific campaign (repeatable)Original addition
Currently installed foreign packagesBatch pacman -Qmq query against the merged campaign lists, exact-matchcommonsourcecs fork
Date window filtering (Jun 9-12)Per-campaign install-date / log-date recency filters (override via env vars or CLI)commonsourcecs fork
Historical pacman.log scanningScans pacman.log* for install eventsKacper-Kondracki fork
Compressed log support (.gz / .xz / .zst / .bz2)Reads rotated/compressed logsKacper-Kondracki fork
~1935 known compromised packages (live via --refresh)Bundled list per campaign, refreshable from upstream per campaignConsolidated from all sources + HedgeDoc
CHAOS RAT (July 2025) campaignchaos-rat campaign with own window and campaign_tag labelsSOURCES.md (CHAOS RAT)
Russian spam packagesrussian-spam campaign — static list, no date windowOriginal addition
systemd persistence check*.service units with Restart=always + RestartSec=30Original addition
eBPF rootkit check/sys/fs/bpf/hidden_* maps (requires root)Original addition
npm cache checkPackages in malicious_npm_packages.txt in npm cache / global node_modules (incl. fnm per-version globals)Original addition
bun cache checkSame packages in the bun cacheOriginal addition
yarn cache checkYarn Classic v1 + Yarn Berry v2+, incl. fnm per-version globalsOriginal addition
pnpm store checkglobal installs + metadata cache + dlx cacheOriginal addition
--refresh flagPulls live lists from each campaign’s refresh URL (e.g., official Arch Linux HedgeDoc)PR #8 (drbbgh)
Configurable date window via env vars or CLISTART_DATE / END_DATE / CHAOS_START_DATE / CHAOS_END_DATE env vars or --start-date / --end-date CLI flagsKacper-Kondracki fork
Color outputANSI colors in all print functions, gated by stdout.isatty()Original addition

The detection data lives under data/campaigns/ — one folder per campaign with its package lists, IOCs, and accounts. Everything is referenced by data/campaigns.json and resolved relative to the checkout.

Exit Codes

Uses a Nagios-compatible scheme:

CodeConstantMeaning
0ExitCode.OKClean — no indicators found
1ExitCode.WARNINGScan completed with warnings (log issues, missing files)
2ExitCode.CRITICALInfected packages or artifacts detected
3ExitCode.UNKNOWNFatal configuration or I/O error (cannot scan)

Package-manager cache checks

The malware is delivered via npm install atomic-lockfile, bun install js-digest, or lockfile-js. These optional flags scan each package manager’s global cache and global installs for the names in malicious_npm_packages.txt:

FlagCovers
--check-npm-cachenpm cache (npm cache ls) + global node_modules (npm root -g)
--check-bun-cachebun cache (bun pm cache)
--check-yarn-cacheYarn Classic v1 (yarn cache dir, yarn global dir) and Yarn Berry v2+ (global cache ~/.yarn/berry/cache, the default since Yarn 4, plus ~/.cache/yarn)
--check-pnpm-cachepnpm global installs (pnpm root -g + the global store under $PNPM_HOME/~/.local/share/pnpm), the metadata cache (~/.cache/pnpm/metadata*//[@scope/].json — the metadata* version suffix varies across pnpm releases, all are scanned), and the dlx cache (~/.cache/pnpm/dlx)
--check-ioc-filesScans the filesystem for known IOC files by path and SHA256 (e.g., ~/.local/bin/sudo password grabber). Implied by --full. Hits raise exit code 2.

--full enables all five (plus the systemd and eBPF checks).

Scope: like the npm/bun checks, this inspects the global cache only — not per-project caches. A repo opting into Berry’s enableGlobalCache: false keeps its cache in a local .yarn/cache/; scan that project root directly if needed.

Substring matching, by design. Names are matched as a substring of each cache entry — format-agnostic, so no hit is missed across managers/versions. A short or generic name in npm-packages.txt can thus over-match (e.g. react → @radix-ui-react-*); the current names don’t, and the per-hit count + sample paths make any noise easy to spot.

fnm note: fnm installs a separate Node — with its own global node_modules — per version. A malicious global install under an inactive Node version is invisible to a plain npm root -g / yarn global dir. The npm and yarn checks therefore also walk every installed version’s global prefix (/node-versions//installation/lib/node_modules), honoring $FNM_DIR and falling back to ~/.local/share/fnm then ~/.fnm. (bun and pnpm are unaffected — bun keeps globals in ~/.bun and pnpm in $PNPM_HOME, both independent of fnm.)

pnpm note: pnpm’s content-addressable store (/v*/files, /v*/index) is hash-named and does not preserve package names, so it cannot be matched by name and is deliberately not scanned (doing so would yield nothing useful). The check instead targets the name-preserving locations: global installs, the metadata cache (a hit means the package was at least resolved/fetched), and the dlx cache.

Tests

python -m unittest discover -s aur_check/tests/ -t .

Standard library only — the suite runs without an Arch system, pacman, npm or bun.

See DEVELOPING.md for the development guide: how to run, test, lint, and type-check the tool, plus the code conventions.

Repository Structure

aur-malware-check/
├── README.md                  # This file
├── DEVELOPING.md              # Development guide (running, testing, conventions)
├── pyproject.toml             # Tooling config (ruff, mypy)
├── CHANGELOG.md               # Version history
├── data/
│   ├── campaigns.json         # Campaign definitions (index — paths into campaigns/)
│   └── campaigns/             # Self-contained campaign packages
│       ├── aur-infected/
│       │   ├── packages.txt           # Compromised AUR packages (--refresh)
│       │   ├── packages-extra.txt     # Supplementary packages (survives --refresh)
│       │   ├── npm-packages.txt       # Malicious npm package names (cache checks)
│       │   ├── SOURCES.md             # Annotated source references
│       │   ├── timeline.md            # Incident timeline, attack vectors, capabilities
│       │   ├── iocs.json              # Structured IOCs (hashes, C2, persistence, eBPF)
│       │   ├── iocs.txt               # Indicators of Compromise (prose)
│       │   └── accounts.json          # Attacker accounts (tracking status)
│       ├── chaos-rat/
│       │   ├── packages.txt           # CHAOS RAT packages
│       │   ├── SOURCES.md             # Annotated source references
│       │   ├── iocs.json              # {} — no known IOCs
│       │   └── accounts.json          # {} — no known accounts
│       └── russian-spam/
│           ├── packages.txt           # Russian spam packages
│           ├── SOURCES.md             # Annotated source references
│           ├── iocs.json              # {} — no known IOCs
│           └── accounts.json          # {} — no known accounts
├── aur_check/                 # Python package (the detection tool)
│   ├── __main__.py            # CLI entry point (python -m aur_check)
│   ├── campaign.py            # CampaignConfig dataclass, load/refresh/print helpers
│   ├── constants.py           # ExitCode enum, env var names, defaults, thresholds, paths
│   ├── merger.py              # List fetching/merging (HedgeDoc + custom lists)
│   ├── models.py              # Dataclasses: PackageMatch, ScanResult, etc.
│   ├── log_utils.py           # Pacman log parsing, compressed file support
│   ├── scanners/              # AurScanner split into focused modules
│   │   ├── __init__.py        # AurScanner class + monkey-patched attachments
│   │   ├── package.py         # check_current(), check_logs()
│   │   ├── system.py          # check_systemd(), check_ebpf(), check_ioc_files()
│   │   └── cache.py           # check_npm/bun/yarn/pnpm_cache()
│   └── tests/                 # unittest suite
└── SOURCES.md                 # Index of per-campaign source references + tooling history

Data files: what’s scanned vs. documentary

FileCampaignConsumed by scanner?How it’s kept up to date
data/campaigns.json— (index)✅ Declares campaigns, their lists, windows, refresh URLs, sources--refresh-campaigns fetches from upstream; static in repo as fallback
data/campaigns/aur-infected/packages.txtaur-infected✅ AUR package checks--refresh fetches the official HedgeDoc list (aur-infected campaign example)
data/campaigns/aur-infected/packages-extra.txtaur-infected✅ AUR package checksSupplementary list, survives --refresh
data/campaigns/aur-infected/npm-packages.txtaur-infected (as npm_lists)✅ npm/bun/yarn/pnpm cache checksHand-curated — no machine-readable feed exists for this campaign (see below)
data/campaigns/aur-infected/iocs.jsonaur-infected❌ documentaryStructured extraction from iocs.txt on 2026-06-24
data/campaigns/aur-infected/iocs.txtaur-infected❌ documentaryHand-curated, prose with extra data compared to the JSON
data/campaigns/aur-infected/accounts.jsonaur-infected❌ documentaryHand-curated
data/campaigns/aur-infected/SOURCES.mdaur-infected❌ documentaryAnnotated source references per campaign
data/campaigns/aur-infected/timeline.mdaur-infected❌ documentaryIncident timeline, attack vectors, malware capabilities
data/campaigns/chaos-rat/packages.txtchaos-rat✅ AUR package checks (own date window)Static — historical July 2025 campaign
data/campaigns/chaos-rat/SOURCES.mdchaos-rat❌ documentaryAnnotated source references per campaign
data/campaigns/russian-spam/packages.txtrussian-spam✅ AUR package checks (no date window)Static — maintained by hand
data/campaigns/russian-spam/SOURCES.mdrussian-spam❌ documentaryAnnotated source references per campaign

Why the npm list isn’t auto-fetched: the malicious npm package names (atomic-lockfile, js-digest, lockfile-js, …) were pulled from npm and are documented only in prose on Socket.dev / Sonatype. They are not present in OSV.dev or the GitHub Advisory Database under these names, so there is no structured feed to fetch. The list is maintained by hand with provenance in SOURCES.md §2. Re-check those sources when adding names.

Campaign Data

Per-campaign files live in data/campaigns//:

Not all files are always present, they are added if respective info is present.

FileContent
SOURCES.mdAnnotated source references (announcements, analysis, mailing list threads)
timeline.mdIncident timeline, attack vectors, malware capabilities
iocs.jsonStructured IOCs (hashes, C2, persistence, accounts)
iocs.txtProse-style IOCs for quick reference
accounts.jsonAttacker AUR accounts with source provenance
packages.txtKnown malicious packages for the campaign
packages-extra.txtSupplementary package list (survives --refresh)
npm-packages.txtMalicious npm/bun packages used as payload

Run python -m aur_check --list-campaigns for all source URLs with dates and comments.

What to Do If Infected

  1. Preserve the system: Do not power off - use forensic acquisition with trusted media
  2. Rotate ALL credentials: Discord, GitHub, npm, Slack, Teams, SSH keys, Vault tokens, cloud provider keys
  3. Check for persistence: systemctl list-units --type=service --state=running (check for unknown services)
  4. Check for eBPF rootkit: ls -la /sys/fs/bpf/hidden_*
  5. Clean with trusted media: Boot from Arch ISO, mount filesystem, remove malicious systemd units
  6. Consider reinstallation: The rootkit makes the system untrustworthy
  7. Report findings: https://lists.archlinux.org/archives/list/aur-general@lists.archlinux.org/

Acknowledgments

Thanks to Kidev, BrianCArnold, commonsourcecs, Kacper-Kondracki, and quantenProjects for the initial detection scripts that started this project.

Thanks also to Clément Gayot (PRs #28, #29, #38, #39, #41), psyke089 (PR #45), Gustavo Matheus (PR #30), drbb (PR #8), Lion Wolf (PR #7), aconite33 (PR #37), Rohit K. Yadav (PR #9), 1mercdev (PR #12), Elias Oelschner (PR #25), and Henry (PR #18), dwaycik (PR #46) for their contributions via pull requests.

License

Community tools - no warranty. Use at your own risk.

Star History

Official distribution

获取与安装

暂未发现可确认的官方软件包地址

当前 README 快照没有出现 npm、PyPI、Crates.io、pub.dev 等官方包页链接。本站不会根据仓库名称猜测下载地址。

本站不托管项目文件;需要安装时,请以项目维护者发布的官方文档为准。

使用前核验

本站保存公开资料用于阅读,不代表安全审计或功能背书。安装前请核对许可证、依赖来源和发布签名,不要直接运行来源不明的二进制文件或高权限脚本。