Changelog

Current channels: stable 3.0.0 (aur-scanner / ks-aur-scanner) and the release-candidate channel (aur-scanner-rc), which builds the current stable tag between candidates. See Install for how the channels differ. Every release tag is GPG-signed — verify with git verify-tag v<version>.

This page tracks notable changes. The format follows Keep a Changelog; full per-release notes live on GitHub Releases.

3.0.0 — 2026-11-10 (stable)

3.0.0 closes a set of ways a crafted package could hide code from the scanner in 2.2.2 and earlier, and changes how the scanner treats anything it can’t model with certainty: it reports the uncertainty instead of trusting it. Results differ from 2.x on some real packages and the library API changed, so this is a major release. 25 new detection codes (142 → 167). Upgrade from any 2.x version with paru -S aur-scanner. Details on What’s in 3.0.

Upgrading from 2.x

  • Expect new findings. Printed text can no longer hide a High or Critical finding. A .install file that prints sudo systemctl enable ... instructions in a heredoc, or through its own note() helper, is now reported at the rule’s severity. A one-line echo "..." of a plain literal is reported as the Medium META-008 instead (see Changed). Packages that commit opaque archives get a Medium SCAN-002. At the default gate (AUR_SCAN_SEVERITY=high), a few packages that installed quietly under 2.2.2 now stop at the prompt: review the finding and confirm, or build deliberately with aur-scan install --force. If you enabled the pacman hook, it aborts the transaction on a new Critical finding; High findings print a warning.
  • The whole package directory is scanned, not only the files the PKGBUILD names, so a stray file in an AUR repo can produce findings.
  • The example rule no longer loads. EXAMPLE-001 moved out of rules.d/ to /usr/share/aur-scanner/example-rule.toml. Copy it into a rules directory if you used it as a template.
  • History records use a new key (rule, file and a hash of the normalized snippet). Records written by 2.x are still read, falling back to rule IDs, so the first diff after upgrading reports nothing spurious.
  • Library users (aur-scanner-core): AnalysisContext and ParsedPkgbuild have new fields, informational_lines is replaced by the Inert classification, and pkgfiles::collect types files differently. Code that builds these structs by hand needs updating.

Security

Each item below is a way a package could get code past 2.2.2. All of them are fixed in 3.0.0. Advisories with full details will follow.

  • Shell-lexing blind spots. A fake heredoc opener (echo "see <<END", cat <<END-OF-NOTES, "E"OF, a numeric delimiter, $(cat <<X ...) inside double quotes) or a comment ending in \ could hide every following line from every analyzer. Heredocs and comments are now handled by one quote-, comment-, ${ }- and $( )-aware lexer, and a heredoc whose terminator never arrives isn’t treated as text. More importantly, no printed text (a heredoc body or a print line) can hide a High or Critical finding any more, so a future desync costs at most a false positive. Pipe-to-shell sinks also cover |&, a pipe at the end of a line, . /dev/stdin, xargs ... bash -c, tee >(bash) and bash < <(curl ...).
  • Obfuscated command names. Empty insertions (n$()pm, npm ``, n${unset}pm), $"..." quoting, parameter-expansion operators (${v:1:3}, ${v//x/}, ${v#p}, ${v,,}, ${v:-npm}, ${a[1]}), +=, printf -v, read <<<, tr rot13, brace lists, aliases, helper functions called with literal arguments, assignments made in another function and variables set in a sourced sidecar are now resolved before the rules run. An unresolvable command word next to a fetch-and-run or an install verb is reported as OBF-020. The install-hook package-manager rules (ATOMIC-002, INSTALL-004) match options before the subcommand (npm -g install, npm --prefix /usr install) and the npm, yarn, pnpm, bun, pipx and uv aliases (npm in, bun a, uvx, …).
  • Scriptlets the scanner didn’t type as scriptlets. install= inside a split package’s package_<name>() used the first pkgname, operators and chained variables weren’t evaluated, and declare/eval/&& forms weren’t seen, so the payload scriptlet was scanned without the install-script rules. Scriptlet typing no longer depends on resolving install=: every file install= could name under any package name, every *.install, and every file that defines pre_/post_ install/upgrade/remove is scanned as a scriptlet. A name that can’t be resolved soundly raises META-007 (High).
  • Files the scanner never read. Committed local archives, NUL-bearing files, files whose name or header looked like media or a certificate, and files in subdirectories were skipped. Every file in the package tree is now read (bounded: depth 8, 4096 files, 20 s; hitting a bound is a Critical SCAN-001). Plain tar members are scanned in memory; a tar outside a strict, well-understood subset raises SCAN-004 (High), a member path that escapes raises SCAN-003 (High), and an archive the scanner can’t open raises SCAN-002 (Medium, not blocking by default). Arch-specific arrays (source_x86_64, sha256sums_aarch64, depends_x86_64, …) are folded in the way makepkg does, so a SKIP on an arch-specific download is reported, and a checksum array can no longer be silenced by one uncountable element or a "SKIP${unset}" entry.
  • Change detection. aur-scan diff and the history store keyed findings by rule ID only and hashed only *.install/*.hook files, so a second hit of a rule already raised, a scriptlet named through install=foo.scr, or a changed C2 address went unnoticed. Findings are now keyed by rule, file and normalized snippet (only the package’s own version-shaped pkgver is masked), and every file typed as a scriptlet is hashed.
  • Denial of service. Inputs that made 2.2.2 run for minutes (self-referential variables, backtracking globs, very long lines, deep $( nests, many heredocs or helper functions) are bounded and linear. A panic in any stage becomes a Critical SCAN-001 for that package only, and the per-package deadline now fires and exits promptly.

Added

  • Live-path writes outside $pkgdir: PERSIST-007 (authorized_keys, Critical), PERSIST-008 (AI coding-assistant config and hook files such as ~/.claude/settings.json, Critical), PERSIST-009 (pacman hook written outside the package, Critical), PERSIST-010 (udev rule written outside the package, High), TAMPER-018 (/etc/hosts, resolv.conf, nsswitch.conf, High), TAMPER-019/TAMPER-020 (git core.fsmonitor and core.hooksPath, High) and TAMPER-021 (the same system-wide, Critical). Directory destinations (cp authorized_keys /root/.ssh/, install -t) count.
  • Shipped hooks and udev rules, recognised by content: PERSIST-011 (pacman hook on every transaction, Medium), PERSIST-012 (one that fetches, runs a shell or uses a temp or home path, High), PERSIST-013 (udev rule that runs a program, Medium), PERSIST-014 (one that runs a shell, fetches or uses a temp or home path, High) and PERSIST-015 (hook or rule content generated at build time, Medium).
  • Build-time installs: ATOMIC-005 (a named npm/pnpm/yarn/bun package or npx -y/dlx fetch in a PKGBUILD function, Medium), ATOMIC-006 (one from a URL or git remote, High) and ATOMIC-007 (bare npx/bunx/exec, Low).
  • EXFIL-010 (Medium): .onion hosts and the local Tor SOCKS port.
  • SRC-011 (High): references above the package directory ($startdir/.., $srcdir/../..), which can run files nobody reviewed.
  • CHK-009 (Medium): checksum alignment can’t be determined.
  • META-007 (High), META-008 (Medium), OBF-020 (High), SCAN-002 (Medium), SCAN-003 (High) and SCAN-004 (High), described above.

Changed

  • META-008, install messages. A one-line echo/printf (or, in a PKGBUILD, msg/msg2/plain/warning/error) whose only argument is one literal quoted string with no $, backtick or redirection, with nothing before it left open, is reported as the Medium META-008 naming the rule it matched, instead of that rule’s High or Critical. The exemption is off for the whole package if any printer is defined, aliased or enabled, or if eval, trap, shopt, BASH_ALIASES, command_not_found_handle, BASH_ENV or a command-position source appears. Heredocs are never exempt.
  • FUNC-001 also covers pkgver(), check() and executed top-level code, and follows helper functions through the whole call chain. prepare() isn’t covered, because fetching there is what the Arch packaging guidelines ask for.
  • Rule and IOC loading warnings are also emitted as tracing events (target aur_scanner_core::loud) for embedders; the stderr line stays.
  • Every published crate ships the license text; the CLI and plugin crates no longer ship tests that only run inside the repository.
  • Dependencies moved to their current major versions: thiserror 2, toml 1.2, reqwest 0.13 (still native-tls), colored 3, base64 0.23 and dirs 7. Unused dependencies were removed and tokio features trimmed. sha2 stays on 0.10 because 0.11 needs Rust 1.88, above the 1.85 MSRV. The workspace uses resolver 3.

Fixed

  • False positives removed on real packages: CHK-006 count mismatches from unexpanded {,.sig}/{,.asc} brace lists (1password, cursor-bin, openssl-1.1, rustdesk, units and others), CHK-005 on packages whose binaries are pinned in arch-specific arrays (claude-code), CHK-008 on checksums held in variables, and SRC-005 on packages whose sources are only in arch-specific arrays (localsend-bin).
  • aur-scanner-git builds main again: a merge whose tree is identical to a release-key-signed PR head’s tree is trusted, since that signature covers the whole tree. Merges that add an edit or merge an unsigned head are still refused.
  • aur-scan scan on very large or crafted inputs no longer runs for minutes.

Known limitations

Tracked for 3.0.1:

  • A Makefile recipe added by a patch that builds a command from Make variables ($(F)$(G) url | $(SHELL)) isn’t resolved.
  • A README that documents curl ... | sh in a code block is reported at the rule’s severity.
  • PERSIST-006 reports systemd drop-ins installed into $pkgdir (nvidia-580xx-utils).
  • Synthetic inputs near the 2 MiB file cap take several seconds.

2.2.2 — 2026-11-09 (stable)

Upgrade with paru -S aur-scanner.

Security

  • check scans the packages an AUR helper can substitute for a dependency. A dependency was scanned only under its exact name, but helpers offer every AUR package that provides it, and a dependency cycle forces the substitute: xmlada needs gprbuild, and gprbuild-bootstrap is what fills it. check, and so the shell integration and aur-scan-wrap, now resolves and scans those providers too and lists them in the notes. install builds exact names and is unchanged. Reported by @kohlrak (GHSA-xp97-gh99-hghq).

Fixed

  • PERSIST-002 no longer fires on REUSE.toml. The "*.timer" glob in the REUSE licence file from the standard Arch packaging template was reported as a Critical systemd timer and, with the pacman hook on AbortOnFail, blocked the install. A bare *.timer glob no longer matches on its own; named timer units and timer globs copied into systemd directories still do. Thanks to @salvoza (#54) and @innateessence (#56).

Added

  • PERSIST-002 also catches a timer enabled by linking it into timers.target.wants, and transient timers from systemd-run --on-calendar, --on-boot and the other --on-* triggers.

2.2.1 — 2026-11-09 (stable)

A security fix to change detection and to variable resolution. Everyone on 2.2.0 should upgrade.

Security

  • Scan history is keyed by the package, not by what its PKGBUILD claims. Baselines were stored under the PKGBUILD’s raw pkgname line, which the author controls and the parser doesn’t expand. Packages written as pkgname="$_pkgname" shared one record and raised false DIFF-003 warnings about each other, and a package declaring another package’s name could replace that package’s baseline and silence the next real change. Records are now filed under the package base the resolver got from the AUR; illegal names are refused, a record filed under the wrong name is discarded, and old records such as $_pkgname.json are pruned. Expect one quiet first scan for packages whose record was filed under a different name. Reported by AD, with a clean reproduction (GHSA-9m24-g9qq-3v4r).
  • A decoded line break can no longer hide a payload. A printf value containing \n, used once, shifted the resolver’s line-aligned output, so a variable-built curl | sh below it scanned clean. Line breaks in substituted values are now flattened (GHSA-w3p6-4wj2-7q48).
  • tokio 1.54.1 (RUSTSEC-2026-0386), and futures 0.3.34 and hyper 1.12.0, which drop the unmaintained pin-utils (RUSTSEC-2026-0441).

2.2.0 — 2026-10-02 (stable)

Stable release of the 2.2 line: everything in rc.1 and rc.2 below, plus the fixes from an internal audit of rc.2 against its own documentation. Every fail-open path below was reproduced before it was changed and ships with a test that failed on rc.2. Four new detection codes (138 → 142): SCAN-001, TI-UNCHECKED-001, OBF-012 and PRIV-009. Install with paru -S aur-scanner. Details in the 2.2 section of What’s in 3.0.

Security

  • A package file the scanner could not read no longer counts as clean. One non-UTF-8 byte, or padding past the 2 MiB cap, made an .install, .hook or local source= script drop out of the scan with only a log line, so a curl | bash scriptlet reported “No security issues found”. Files are now decoded lossily and read through a bounded reader; anything oversized, unreadable, symlinked or not a regular file (a FIFO, a link to /dev/zero) produces the new Critical SCAN-001, which every gate treats as unreviewed and install --force cannot override. Symlinks are never followed.
  • A rules.d file could silently switch off a built-in detection. A community rule reusing a built-in id replaced it. Rules that reuse an existing id are now rejected with a warning, and as root only /usr/share and /etc rule directories are read.
  • Dependencies were trusted by default. Any name the AUR RPC didn’t return was shown as [repo] (official, trusted) without asking pacman, including nonexistent names and dependencies only an AUR package provides. Classification now asks pacman; AUR providers are found and scanned; anything left is [UNRESOLVED] and fails the run. A tree cut short by the depth or size cap fails too.
  • aur-scan-wrap passed what it couldn’t parse straight to the helper. paru -S aur/pkg installed unscanned with exit 0. The wrapper now resolves repo/name operands, blocks anything else it can’t validate, and delegates to aur-scan check, so it scans the full dependency tree and the upgrade set (-Syu, -Sua, bare paru/yay). The Nushell integration, which routes through the wrapper, gets the same gate.
  • Install gates that failed open: a failed -Quaq upgrade query, a missing aur-scan, and local builds (-B <dir>, -Ui, -U <dir>) all ran the helper unscanned in bash, zsh, fish, Nushell and the wrapper. They are now refused or scanned from disk; <helper>-unsafe remains the deliberate bypass.
  • check let piped input approve findings and ignored --fail-on below High in interactive mode. The prompt now appears whenever the gate trips, and only a real terminal can answer it.
  • min_severity disabled gates. It filtered findings before every gate, so min_severity = "critical" removed High findings from the wrapper prompt, install --gate high and JSON. It is now display-only.
  • Split packages: only the last install= was scanned. Every install= is now scanned.
  • Detection hardening for staged and indirect fetch-and-execute forms, variable indirection across and within functions, inline encoded payloads (bounded decoding), cron persistence, and helper functions that execute their arguments (new Critical OBF-012).
  • Files the scan never read: every text file in the package directory is now scanned, plus any path the PKGBUILD reaches through $startdir or $srcdir, not just files with a script extension. A declared install= that is missing or a directory is SCAN-001.
  • Kernel modules loaded from an install scriptlet were not checked at all. Module commands there are now PRIV-005, and loading a module the package itself ships is the new Critical PRIV-009.
  • Report and help flags bypassed the wrapper and shell gates. --stats, --news, --order, --comments, --gendb or --help anywhere on the command line made paru -S pkg --stats install unscanned.
  • Search-menu installs are refused by default. paru <term>, yay <term>, yay -Y… (including -Ys/-Yi) and -S --interactive pick the package after the scan. Name it with -S <name>, or set AUR_SCAN_ALLOW_MENU=1.

Fixed

  • Pacman hook found nothing in common setups and exited 0: doas, run0, pkexec, root shells, home directories outside /home, and split packages. It now finds the invoking user through sudo, doas, pkexec or the login uid, matches split packages through .SRCINFO, reads paru/yay cache settings, warns per foreign package it couldn’t scan or whose cached version differs, and aborts instead in strict mode (AUR_SCAN_HOOK_STRICT=1 or /etc/aur-scanner/hook-strict). Its split-package lookup always consults the .SRCINFO index, and differing candidate versions are scanned and reported.
  • install printed “All packages built and installed” after silently skipping a repo package it was asked for; it now refuses non-AUR roots. Declining or a missing terminal exits non-zero, dependencies are installed --asdeps, and the scanned files are hashed again right before makepkg. Packages whose sources makepkg fetches unpinned after the scan are listed.
  • Change detection never ran for AUR packages in check: the fetched copy was deleted before the history comparison read it.
  • git clone now runs with a scrubbed environment, ignores system and global git config, verifies objects, and is time-bounded.
  • timeout_seconds was documented but read by nothing. It now bounds the AUR RPC, the clone, and each package’s fetch and scan; a timeout fails closed.
  • JSON and SARIF on stdout were corrupted by log lines; logs go to stderr. SARIF rules are unique and indexed, findings without a line no longer get a made-up startLine, and URIs are relative to the scanned root.
  • CycloneDX SBOM: unique bom-refs, no dangling dependsOn, percent-encoded purls (epochs), and the real repository name instead of a guessed one. metadata.component now describes the install request and depends on the requested packages, which are all listed in components; a single-package SBOM no longer ends up with an empty components list.
  • Duplicate findings when two patterns of one rule matched the same line.
  • Rule loader: one bad regex no longer disables the whole directory; unknown keys and pattern-less rules are rejected; file_types = ["source_file"] now works. Community rule ids are trimmed and uppercased before the collision check and must match the ACME-001 shape.
  • aur-scan rules was a stale hand-written list; it is generated from the catalog. codes rejects an unknown --format or --category.
  • aur-scan system always exited 0. It now fails on --fail-on (default Critical), IOC matches, and packages it could not scan, which it lists.
  • Threat intel: VirusTotal lookups default to 4 per scan in declared order (vt_max_lookups), stop after a rate-limit reply, and report what went unchecked as TI-UNCHECKED-001 instead of nothing. Response bodies are capped and credentials in URLs are stripped before lookup.
  • A malformed system IOC override and an explicit rules_path that fails to load are hard errors, like the config file. An analyzer error fails the scan instead of dropping that analyzer’s findings.
  • False positives at High/Critical on ordinary packages: a setuid chrome-sandbox in Electron -bin packages (now Low), systemctl daemon-reload, shipping a modprobe.d file, and reading ${XDG_CONFIG_HOME:-~/.config}.
  • The printed dependency tree is nested instead of flat.
  • check --local in an owned namespace raised a Critical SQUAT-004 even when the configured maintainer was right. The real AUR maintainer is now looked up; a local package with no AUR record is reported at High as “publisher cannot be verified” instead of as a confirmed impostor.
  • A spawn allowlist test replaces the string grep that guarded the static-only invariant: every subprocess the shipped code can start must be on a reviewed list. ldd is no longer treated as an inert command, since it runs its target through the loader.
  • Upgrade scanning forwards the helper flags that change the update set (--aururl, --config, --ignore, --root, …) to -Quaq, and with --devel also scans installed VCS packages. More value-taking options are recognized so their values aren’t mistaken for packages, and aur-scan-wrap --help prints usage.
  • Hidden gate findings: when --severity hides a finding that trips --fail-on, the text output now says so instead of “No security issues found”. JSON and SARIF always carry every finding.

Changed

  • Minimum supported Rust is 1.85, declared in rust-version and checked in CI; CI builds with --locked.
  • The root and -git PKGBUILDs conflict with aur-scanner-rc.
  • Documentation corrected where it overstated the code: runtime dependencies (gcc-libs and openssl, dynamically linked), the pacman hook’s role and install path (an opt-in post-build backstop in /etc/pacman.d/hooks/), the install TOCTOU guarantee, threat-intel egress and cache wording, what the GitHub rulesets enforce, and the -git package’s signature check.

2.2.0-rc.2 — 2026-10-01 (release candidate — superseded by 2.2.0)

Second 2.2.0 release candidate. No new detection codes (still 138); it gets testers the fixes made since rc.1, including the 2.1.1 security fixes merged forward. Install aur-scanner-rc.

Security

  • A comment could hide a download from FUNC-001. The check skipped the whole function if # curl appeared anywhere in it, so one comment line silenced a real curl on the next. It now works line by line and skips only comment lines; a tab after the command, a quoted "curl", and ${wget} are caught too.

  • The pacman hook no longer reads a user-writable config as root. As root it now reads /etc/aur-scanner/config.toml and nothing else. Before, a hostile build() could drop broken TOML in ~/.config/aur-scanner/ and, because a malformed security config is a hard error, wedge every pacman transaction after it.

Fixed

  • FUNC-001 false positive on build targets like cmake --target libcurl (#35, thanks @gulamovzavohir02-glitch). /usr/bin/curl, \curl and ;curl still fire.
  • aur-scanner-git builds again. It refused every GitHub merge commit, since GitHub signs those with its own key. It now accepts main only when every merge is a clean merge of a release-key-signed PR head, and checks the key by fingerprint rather than any key in your keyring.
  • ATOMIC-004 missed the natural form of a sudo shim. cp payload /usr/local/bin/sudo and install -Dm755 stealer "$HOME/.local/bin/sudo" now match; the rule anchors on the PATH directory, not on how home was spelled.
  • PERSIST-003 was narrowed too far after #21: echo '…' | crontab - and friends passed clean. They match again, and removing a stale cron entry still stays silent. A separate false positive on python-python-crontab is gone; measured against 503 live PKGBUILDs, the rule now fires on one, dcron-git, which does install cron files.
  • An empty XDG_CONFIG_HOME no longer drops the user config path.
  • A malformed config no longer breaks the commands that diagnose it. explain, rules, ioc and codes run before config loading again.
  • scanned_files lists every file read (hook scriptlets, local source=() scripts, committed binaries), so SARIF consumers get a complete manifest.

2.1.1 — 2026-10-01 (stable)

Security patch for the 2.1 line. No detection, rule, or output changes, so upgrading is safe. Install with paru -S aur-scanner.

Security

  • aur-scan check --no-confirm exited 0 on a Critical when no --fail-on was given, which is how the shell integrations ran it with AUR_SCAN_INTERACTIVE=0. A non-interactive check now fails closed on Critical by itself, and the bash, zsh and fish integrations pass --fail-on "$AUR_SCAN_SEVERITY" when non-interactive. Interactive use is unchanged. Re-source your shell integration after upgrading.
  • The pacman hook read a user-writable config as root, so broken TOML in ~/.config/aur-scanner/ could abort every pacman transaction. As root it now reads /etc/aur-scanner/config.toml only.

Fixed

  • An empty XDG_CONFIG_HOME falls back to ~/.config instead of disabling the user config (#25).

2.2.0-rc.1 — 2026-09-10 (release candidate — superseded by rc.2)

Change detection, name impersonation, ownership signals and static binary analysis: 20 new detection codes (118 → 138), and every scanning path rewired. Install aur-scanner-rc to test it. The full tour, with the measurements behind each threshold, is in the 2.2 section of What’s in 3.0.

Added

  • Change detection (DIFF-001..004) on check and install: new findings since the last scan, ownership moved, upstream moved, install script added or changed. Silent on a first scan and on a plain version bump.
  • aur-scan diff <old> <new>: stateless comparison of two package directories. --fail-on trips only on newly added findings.
  • Name impersonation (SQUAT-001..004) and [[owned_namespaces]] to declare the names you publish.
  • Ownership signals (OWN-001..004): orphaned, orphaned and out-of-date, stale out-of-date flag, new with no community validation.
  • Static binary analysis (BIN-001..005) of executables committed to the AUR repo, with a bounds-checked ELF reader. Nothing is executed; ldd is off-limits.
  • SRC-010 (source from a different owner’s copy of upstream), DEEP-003 (Unicode bidi / Trojan Source), ESCAPE-001 (writes outside $srcdir/$pkgdir), and local source=() files are now read.
  • PERM-001/PERM-002 world-writable rules, shipped as a community TOML file (#8).
  • aur-scan completions <shell> for bash, zsh and fish, installed by the packages.
  • aur-scan version validates the config and exits 2 if it’s broken.

Fixed

  • aur-scan check --no-confirm exited 0 on a Critical when --fail-on wasn’t passed, which is how the shell integrations called it. With AUR_SCAN_INTERACTIVE=0 the gate was a no-op. AUR_SCAN_SEVERITY is now both the display floor and the block threshold.
  • Registry context reached only check. install, the wrapper, system and the hook ran a smaller analyzer set; it’s now a required argument everywhere.
  • AUR_SCAN_MODE=install was the weaker mode while documented as stronger.
  • SHELL-002 false positive on the nc at the end of MEGAsync (#32).
  • CHK-004/CHK-005 name the sources they count (#31).
  • Terminal escape injection: package-controlled text in findings is sanitized before printing.
  • Shell integrations wrote their banner to stdout, breaking scp, rsync and ssh host cmd.
  • Scan history hardening: poisoning via a --local directory’s self-declared name, a torn-write race between concurrent scans, unbounded growth, and a world-writable fallback path.
  • Every config key is validated, including inside [threat_intel] and [cache]. A typo used to silently do nothing.
  • Packages installed only example.toml from rules.d/; every shipped rule file installs now.

Known gaps: decompression bombs and hostile makedepends aren’t detected, and VirusTotal keys off declared sha256sums, not a hash of a committed binary.

2.1.0 — 2026-09-10 (stable)

Promotes 2.1.0-rc.2 unchanged after six weeks of soak: no detection or behavior differences from the RC. Everything in the two candidates below is in this release. Install with paru -S aur-scanner.

Security

  • anyhow 1.0.104, past RUSTSEC-2026-0190 (unsound Error::downcast_mut()).

Changed

  • CI and the advisories workflow on actions/checkout@v5.

2.1.0-rc.2 — 2026-07-28 (release candidate — promoted to 2.1.0)

Same code as 2.1.0-rc.1, plus packaging and CI hygiene (repo-wide cargo fmt, rustfmt as a hard CI gate). Release tags are immutable, so this re-points aur-scanner-rc at the clean tip.

2.1.0-rc.1 — 2026-07-28 (release candidate — promoted to 2.1.0)

Correctness and Atomic Arch coverage depth.

Added

  • Config is found automatically. Without -c, the CLI, the pacman hook, aur-scan-wrap and aur-scan install load the first of $XDG_CONFIG_HOME/aur-scanner/config.toml (or ~/.config/…) and /etc/aur-scanner/config.toml. A present-but-malformed file is a hard error. This is why threat intel looked dead when keys lived only in the system config (#25).
  • [output] config table to choose which fields each finding prints (line, snippet, recommendation, cwe). Display-only: it never changes findings, exit codes, or gates. check compact output now shows file:line.
  • ALPM .hook side scripts in the package directory are read and analyzed like install scriptlets (the Atomic Arch wave-4 delivery path). Never executed.
  • ATOMIC-004: credential-stealing sudo shim in ~/.local/bin and similar.
  • ATOMIC-001 gains the wave-3 package names; ENV-003 covers fish, zsh and profile.d startup paths.

Fixed

  • The wrapper, plugin and install ignored your config. They used built-in defaults, so XDG and /etc settings (threat intel included) never applied when scanning through the wrapper.
  • SRC-004 false positive on raw.githubusercontent.com (#22).
  • CHK-006 counted commented-out sources (#24).
  • PRIV-002 fired on clearing SUID. u-s, g-s and -s no longer fire; setting forms (u+s, =s, octal 2–7xxx) still do (#21). Browser sandbox helpers still set it on purpose, so they still report; see the FAQ.
  • PERSIST-003 fired on cron removal (#21), and ENV-003/HIDDEN-001 fired on install messages that merely mention ~/.bashrc (#15).
  • aur-scanner-git prepare() handles GitHub-signed merge commits (#20).

Security

  • The pacman hook stays fail-closed in multi-package transactions: a Critical aborts the whole transaction. Skipping just the offending package would let a malicious one ride along with clean ones.

2.0.0 — 2026-06-17 (stable)

Major release. Promotes the 1.2.0-rc1 line to stable. The default scan is unchanged — fully offline and static; everything new is opt-in or additive. Install with paru -S aur-scanner.

Added — opt-in threat intelligence

  • VirusTotal & URLhaus lookups. With enable_threat_intel set and your own key, the scanner checks declared sha256sums against VirusTotal and source= URLs against abuse.ch/URLhaus, emitting TI-VT-001 / TI-URLHAUS-001 on a malicious verdict. Off by default — only already-public hashes/URLs are sent, every lookup fails open, and all egress is isolated in one auditable file. See Threat Intelligence. The VirusTotal-by-hash idea came from @SuitablyMysterious (#9).
  • Active verdict cache. The per-user, MAC-authenticated cache now backs threat-intel lookups so repeat scans respect VirusTotal’s request quota; transient failures are never cached as a verdict.

Added — broader coverage & integrations

  • Every maintained AUR helper is covered by the system audit and the pacman hook — yay, paru, pikaur, aura, pakku, trizen, aurutils, rua, pat-aur — at each helper’s real PKGBUILD location. The shell integration also wraps pikaur/trizen/pakku alongside paru/yay.
  • Nushell integration (install/integration.nu) joins bash/zsh/fish.
  • --no-color global flag (and NO_COLOR env) for clean output in logs/CI.

Changed

  • -c/--config now applies to every command, including system — so a threat-intel config can VT-check every installed AUR package, not just scan.

1.2.0-rc1 — 2026-06-17 (release candidate — promoted to 2.0.0)

The release candidate that became 2.0.0 above. Lived in the repository while it baked, then shipped as stable 2.0.0.

1.1.0 — 2026-06-15 (stable)

Stable. Promotes the 1.1.0 release-candidate line and adds a second hardening wave that closes the residual evasion classes from an adversarial self-audit. Install with paru -S aur-scanner.

Detection — evasion classes closed

  • Variable-indirection (taint pass). A fetch/exec hidden behind a shell variable now resolves and is flagged — it used to trip nothing.
  • Case-insensitive analyzers, host-aware URL/IOC matching, and a supply-chain / packaging-metadata analyzer (dependency confusion, core-package displacement, signature theatre, sensitive backup=, malformed hashes).
  • Quote-aware printed-message filtering (a ; inside a quoted echo no longer false-positives).

Hardening

  • Authenticated cache verdicts (per-user MAC), an allowlisted makepkg build environment, --force can never override an unscannable package, and --local only attributes a verdict to a name that provably matches.

Quality

  • A self-adversarial evasion fuzzer runs as a release gate — every malicious fixture is mutated through semantics-preserving evasion transforms and the gate must still block each variant.

Threat reports that informed this line: #2 (@LunarEclipse363) and #10 (@zebulon2).

1.1.0-rc2 — 2026-06-14 (release candidate)

Proactive detection expansion, prompted by a live obfuscated AUR campaign. Try it with paru -S aur-scanner-rc.

Anti-evasion

  • De-obfuscation pass. A new wave hid a bun add <js-payload> in a post_install hook using ANSI-C quoting ($'\x63') and adjacent-quote word-splitting ("b"'u''n') — the previous RC caught it only as a generic high “hex payload.” The scanner now decodes ANSI-C escapes and collapses quote-splitting, then runs every rule against the decoded text, so the whole catalog resists this evasion at once. That sample is now correctly flagged critical.

Detection — catalog 72 → 106 codes

  • Reverse/bind shells beyond bash/nc: perl, php, ruby/lua/awk, node, openssl s_client, mkfifo backpipe, busybox-nc/telnet/ncat-ssl.
  • Exfiltration: DNS tunnelling, HTTP upload, Slack/Teams webhooks, anonymous file-drop/tunnel hosts, environment dumps, cloud/CI credential + keyring/wallet access.
  • Auth & system tampering: privileged-account creation, password changes, writes to the auth database, doas/NOPASSWD, PAM, pacman SigLevel=Never, disabling security controls, rogue CA trust anchors.
  • Supply-chain trust: pacman-key / gpg key imports, package-index overrides, obfuscated IPs in source URLs.
  • More obfuscation & RCE forms: printf assembly, base32/16 decode, interpreter here-strings, sh -c "$(curl)", detached setsid/nohup.

Other

  • aur-scan install cleans up its own build directory after a successful install (--keep-build to retain).

1.1.0-rc1 — 2026-06-13 (release candidate)

Security-hardening release resolving a full security & quality audit. Try it with paru -S aur-scanner-rc; read the behavior changes before pointing automation at it.

⚠️ Behavior changes (read before upgrading)

  • The scanner now fails closed. The paru/yay wrapper and the pacman hook previously continued past a fetch/scan error, a timeout, or a non-interactive prompt; they now deny in those cases. If you drive paru/yay from a script, cron, or CI (no TTY), an install that can’t be fully analyzed is refused rather than silently proceeding. A security gate that fails open is not a gate.
  • scan --format json / --format sarif emit only the machine document on stdout. The human-readable summary moved to stderr, so aur-scan scan --format json | jq works. If you scraped the summary out of stdout, read stderr instead.

Security

  • Input-validation chokepoint for package names/bases — illegal identifiers are rejected before they can become URL path segments or filesystem paths, closing a path-traversal vector and request-injection into the AUR RPC.
  • Network hardening: redirects refused, HTTPS-only enforced, response bodies size-capped (streaming), all RPC URLs percent-encoded.
  • Pacman hook drops root (groups → gid → uid, verified irreversible) before reading user cache files, validates names, and refuses symlinked PKGBUILDs.
  • Detection-evasion fixes: backslash-newline line-continuation splicing; quote- and comment-aware brace scanning (no more echo "}" / # } truncation); broadened reverse-shell and bare crypto-address detection; checksum SKIP-laundering closed across all hash arrays.
  • Dependency advisory RUSTSEC-2026-0007 resolved (bytes → 1.11.1).

Added

  • SRC-007: warns when a VCS source isn’t pinned to a commit (Low).
  • CLI integration suite that runs the real binary against PKGBUILD fixtures.
  • CI (format, clippy-as-error, tests, release build) and cargo-deny supply-chain gating, plus a weekly advisory scan.
  • The rolling aur-scanner-git package now verifies the signed HEAD commit at build time.

Fixed

  • False positive: a printed note "...~/.config/..." message no longer trips HIDDEN-001 (it mentions a path; it doesn’t write one) — seen on google-chrome and visual-studio-code-bin.
  • Parser: source+=(...) appends are no longer dropped; inline comments are handled quote-aware; single-line function bodies are captured.
  • Cache writes are atomic (0600) in an owner-only (0700) directory, entries are key-bound, and a corrupt entry is a miss rather than trusted data.
  • Provenance store distinguishes an absent baseline from a corrupt one.
  • Removed a panicking AurClient::default() and a response unwrap.

1.0.3 and earlier

Released before this changelog was introduced. See the GitHub Releases for 1.0.3 (race-free install, dependency-tree SBOM), 1.0.1 (INSTALL-004 — language package managers in install hooks), and 1.0.0 (first public release).