SkillAgentSearch skills...

Aur Malware Check

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

Install / Use

npx skills add lenucksi/aur-malware-check

Installs into whichever agent you are using.

About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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 / Feature | Detail | Source | |-----------------|--------|--------| | Campaign-based architecture | All campaigns declared in data/campaigns.json with per-campaign lists, date windows, env overrides, and campaign_tag labels | Original addition | | --list-campaigns | Show configured campaigns with lists, windows, env vars, and refresh URLs | Original addition | | --refresh-campaigns | Fetch latest campaigns.json from upstream, validate, show diff, write | Original addition | | --dry-run | With --refresh-campaigns, show diff without writing | Original addition | | --json | Machine-readable output for --list-campaigns and scan results | Original addition | | -l [CAMPAIGN_ID=]PATH | Add an extra package list to a specific campaign (repeatable) | Original addition | | Currently installed foreign packages | Batch pacman -Qmq query against the merged campaign lists, exact-match | commonsourcecs 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 scanning | Scans pacman.log* for install events | Kacper-Kondracki fork | | Compressed log support (.gz / .xz / .zst / .bz2) | Reads rotated/compressed logs | Kacper-Kondracki fork | | ~1935 known compromised packages (live via --refresh) | Bundled list per campaign, refreshable from upstream per campaign | Consolidated from all sources + HedgeDoc | | CHAOS RAT (July 2025) campaign | chaos-rat campaign with own window and campaign_tag labels | SOURCES.md (CHAOS RAT) | | Russian spam packages | russian-spam campaign — static list, no date window | Original addition | | systemd persistence check | *.service units with Restart=always + RestartSec=30 | Original addition | | eBPF rootkit check | /sys/fs/bpf/hidden_* maps (requires root) | Original addition | | npm cache check | Packages in malicious_npm_packages.txt in npm cache / global node_modules (incl. fnm per-version globals) | Original addition | | bun cache check | Same packages in the bun cache | Original addition | | yarn cache check | Yarn Classic v1 + Yarn Berry v2+, incl. fnm per-version globals | Original addition | | pnpm store check | global installs + metadata cache + dlx cache | Original addition | | --refresh flag | Pulls live lists from each campaign's refresh URL (e.g., official Arch Linux HedgeDoc) | PR #8 (drbbgh) | | Configurable date window via env vars or CLI | START_DATE / END_DATE / CHAOS_START_DATE / CHAOS_END_DATE env vars or --start-date / --end-date CLI flags | Kacper-Kondracki fork | | Color output | ANSI 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:

| Code | Constant | Meaning | |------|----------|---------| | 0 | ExitCode.OK | Clean — no indicators found | | 1 | ExitCode.WARNING | Scan completed with warnings (log issues, missing files) | | 2 | ExitCode.CRITICAL | Infected packages or artifacts detected | | 3 | ExitCode.UNKNOWN | Fatal 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:

| Flag | Covers | |------|--------| | --check-npm-cache | npm cache (npm cache ls) + global node_modules (npm root -g) | | --check-bun-cache | bun cache (bun pm cache) | | --check-yarn-cache | Yarn 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-cache | pnpm global installs (pnpm root -g + the global store under $PNPM_HOME/~/.local/share/pnpm), the metadata cache (~/.cache/pnpm/metadata*/<registry>/[@scope/]<pkg>.json — the metadata* version suffix varies across pnpm releases, all are scanned), and the dlx cache (~/.cache/pnpm/dlx) | | --check-ioc-files | Scans 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. | Original addition |

--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 (<fnm-dir>/node-versions/<version>/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 (<store>/v*/files, <store>/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 i

Related Skills

View on GitHub
GitHub Stars2.1k
CategoryDevelopment
Updated39m ago
Forks50

Languages

Python

Security Score

95/100

Audited on Aug 8, 2026

No findings