Commands

aur-scan check — pre-install review

Resolves the full transitive AUR dependency tree, scans every untrusted package, prints a reviewable tree, and can emit a CycloneDX SBOM — all before anything builds. A hijacked package is often a dependency, so the named package alone is not enough.

aur-scan check <package>...
aur-scan check <package> --sbom tree.cdx.json     # write an SBOM
aur-scan check <package> --no-confirm --fail-on high   # CI gate
aur-scan check --local ./mypkg                    # scan exact on-disk bytes
aur-scan check <package> --no-deps                # named packages only, skip the tree
aur-scan check <package> --include-optional       # follow optdepends too

In scripts, pair --no-confirm with --fail-on to set the threshold you mean. Without it, 2.1.1 and later fail closed on a Critical; 2.1.0 and earlier exited 0 even on a Critical.

Gate and prompt. With --no-confirm the run fails at --fail-on (default Critical). Interactively, a tripped gate asks before passing, and only a real terminal can answer: piped or redirected input is a denial, never a yes. Packages that couldn’t be fully analyzed (SCAN-001), fetched or resolved are never offered a prompt; they fail the run.

Dependency tree. The tree is printed nested, each node marked [AUR] (scanned) or [repo] (official, trusted). A dependency counts as [repo] only when pacman says a sync repository satisfies it. A name that only an AUR package provides is resolved through the AUR and every provider is scanned. For an AUR dependency, the other packages that provide its name are scanned too, because your helper can offer any of them instead; they’re listed in the notes. Anything neither can satisfy is [UNRESOLVED] and fails the run, as does a tree cut short by the depth or size cap: an unscanned package is never reported as clean. timeout_seconds bounds each package’s fetch and scan, and a timeout fails closed.

Exact bytes. By default check fetches its own copy of each PKGBUILD and your helper re-clones and builds its own, so the bytes scanned aren’t provably the bytes built. To scan exactly what will be built, fetch once, scan that directory with --local, and build from it:

paru -G mypkg                                     # download the PKGBUILD only
aur-scan check --local mypkg --fail-on critical   # scan those exact bytes
makepkg -D mypkg -si                              # build the same directory

aur-scan install — race-free install

Fetch each AUR package once, scan those exact directories, gate on findings, then build them in dependency order with makepkg. The bytes scanned are the bytes built — no time-of-check/time-of-use gap.

aur-scan install <package>...
aur-scan install <package> --gate high       # block on high+ findings (default: critical)
aur-scan install <package> --noconfirm       # pass --noconfirm to makepkg, skip the build prompt
aur-scan install <package> --include-optional    # follow optdepends too
aur-scan install <package> --sbom tree.cdx.json
aur-scan install <package> --workspace ~/build   # where clones/builds go (default ~/.cache/aur-scan/build)
aur-scan install <package> --keep-build          # keep build dirs after a successful install
aur-scan install <package> --force               # build even though the gate tripped (deliberate)

--force overrides a tripped gate, never an unscannable package: a package it couldn’t read, and any unresolved or ambiguous dependency, still won’t build. It’s the deliberate way through for a package whose finding you’ve reviewed, such as one of the new 3.0.0 findings on a package that installed quietly under 2.x (see Upgrading from 2.x).

What install does and doesn’t cover, exactly: every scanned file is hashed again immediately before makepkg runs, and any change aborts the build. It does not cover what makepkg itself does after the scan: it still downloads source= files, checks out VCS sources and runs pkgver(). Pinned checksums protect the downloads; SKIP checksums and unpinned VCS sources do not, and before building, install lists every package that relies on those.

It installs AUR packages only. A named package that lives in the official repos is refused with a pointer to pacman -S, never silently skipped. Declining the prompt, or running without a terminal and without --noconfirm, exits non-zero, and AUR dependencies are installed --asdeps. It builds each AUR pkgbase with makepkg -si in dependency order; it doesn’t cover paru-specific features like split-package selection or chroot builds (use the shell integration’s gate mode for those).

aur-scan scan — local PKGBUILD

aur-scan scan ./PKGBUILD
aur-scan scan ./my-package/        # directory: every file in the package tree
aur-scan scan ./pkg --format sarif # text | json | sarif

A directory scan reads every file in the package tree (up to depth 8 and 4096 files; a real top-level .git is skipped, symlinks are never followed), because a payload can be run from any of them. Binary and NUL-bearing files are scanned with the NULs removed, and plain tar members are read in memory and reported as archive.tar:member. A file is scanned as an install scriptlet if install= could name it under any package name, if it ends in .install, or if it defines a pre_/post_ scriptlet function, so a scriptlet is never missed because install= was written in an unusual way (META-007 reports a name that can’t be resolved soundly).

What can’t be reviewed is reported instead of skipped: a bound that was hit or a file that can’t be read is SCAN-001 (Critical), an archive that can’t be opened is SCAN-002 (Medium), a tar member whose path escapes is SCAN-003, and a tar the readers would disagree about is SCAN-004 (both High). --severity only trims the text output; if it hides a finding that trips --fail-on, the output says so, and JSON and SARIF always carry every finding.

aur-scan system — audit what’s installed

Cross-references every installed AUR package against the IOC database, scans cached PKGBUILDs, and flags any package that gained risky behavior since the last scan. It exits non-zero when a finding reaches --fail-on (default Critical), when an installed package matches the IOC database, or when a package couldn’t be scanned (those are listed under Errors; packages with no cached PKGBUILD are listed as skipped).

aur-scan system
aur-scan system --rescan     # re-fetch PKGBUILDs from the AUR instead of the local cache
aur-scan system --fail-on high   # change the exit threshold (default: critical)
aur-scan -c ti.toml system   # also VT/URLhaus-check every installed package (opt-in)

aur-scan ioc — indicators of compromise

aur-scan ioc                 # database stats + campaigns
aur-scan ioc --check <name>  # is this a known-malicious indicator?

aur-scan codes / explain

aur-scan codes                       # the full detection catalog
aur-scan codes --format markdown     # or json (md is an alias of markdown)
aur-scan codes --category <category> # filter by category
aur-scan explain EXEC-REMOTE         # detail on one code
aur-scan rules                       # the pattern rules, generated from the catalog

codes rejects an unknown --format or --category. explain, rules, ioc and codes read only the built-in rule table, so they still run when your config is broken (codes also reads rules_path, and falls back to the built-in list with a warning).

aur-scan version

aur-scan version    # scanner version, components and capabilities

Since 2.2.0 it also validates your config and exits 2 if it’s broken, so you can check before an upgrade instead of finding out when the pacman hook aborts.

aur-scan diff and aur-scan completions

Two commands new in 2.2.0. Details in the 2.2 section of What’s in 3.0.

aur-scan diff ./mytool-1.0 ./mytool-1.1              # what changed between two versions
aur-scan diff ./old ./new --fail-on critical         # CI gate: NEWLY ADDED findings only
aur-scan diff ./old ./new --format json              # text (default) | json
aur-scan completions zsh > /usr/share/zsh/site-functions/_aur-scan   # bash | elvish | fish | powershell | zsh

diff keeps no state, so it’s safe in CI; check and install compare against your scan history automatically.

Global flags

These apply to any command:

-c, --config <FILE>     # use this config instead of the default search path
-s, --severity <LEVEL>  # minimum severity to report: critical|high|medium|low|info
    --fail-on <LEVEL>   # (scan) exit non-zero at or above this severity — for CI gates
    --no-color          # disable ANSI color (also honored: the NO_COLOR env var)
-q, --quiet             # only show findings
-v, --verbose

Without -c, aur-scan loads ~/.config/aur-scanner/config.toml (or $XDG_CONFIG_HOME) if it exists, else /etc/aur-scanner/config.toml, else built-in defaults. See Install → Configuration. A config is also how you turn on threat intelligence: optional VirusTotal / URLhaus lookups that are off unless you enable them and supply your own key. A scan with threat intel off makes no network calls beyond fetching from the AUR.

One thing worth knowing

aur-scan only ever reads. It doesn’t source the PKGBUILD, doesn’t run makepkg to peek inside, doesn’t follow a URL it finds, doesn’t run anything it downloads. So when a package tries to pull and run code from somewhere else, it tells you (that’s EXEC-REMOTE, and it gives you the URL) and stops. It won’t go fetch that code, because then the scanner would be the thing running the payload. “We can’t see past this point” is the honest answer, and it’s the safe one.